///|
/// OB001: malformed object; OB002: unsupported object format or file kind;
/// OB003: discovery budget exceeded. Offsets are relative to the object bytes.
pub(all) struct ObjectError {
code : String
offset : Int32
message : String
} derive(Eq, Debug)
///|
pub extend ObjectError with Eq::{equal, not_equal}
///|
pub extend ObjectError with Debug::{to_repr}
///|
priv suberror InvalidObject {
InvalidObject(ObjectError)
}
///|
priv struct ObjectBudget {
mut symbols_left : Int32
mut names_left : Int32
}
///|
fn[T] object_reject(
code : String,
offset : Int32,
message : String,
) -> T raise InvalidObject {
raise InvalidObject({ code, offset, message, })
}
///|
fn object_range(
data : Bytes,
offset : Int32,
size : Int32,
) -> Unit raise InvalidObject {
if offset < 0 ||
size < 0 ||
offset > data.length() ||
size > data.length() - offset {
object_reject("OB001", offset, "object range extends beyond its bytes")
}
}
///|
fn object_number(
data : Bytes,
offset : Int32,
width : Int32,
little : Bool,
maximum : Int32,
) -> Int32 raise InvalidObject {
object_range(data, offset, width)
let mut value = 0
for n = 0; n < width; n = n + 1 {
let digit = data[if little { offset + width - n - 1 } else { offset + n }].to_int()
if digit > maximum || value > (maximum - digit) / 256 {
object_reject(
"OB001", offset, "object integer exceeds its permitted range",
)
}
value = value * 256 + digit
}
value
}
///|
fn object_tag(data : Bytes, offset : Int32, little : Bool) -> UInt32 {
let mut value = 0U
for n = 0; n < 4; n = n + 1 {
value = (value << 8) |
data[if little { offset + 3 - n } else { offset + n }].to_uint()
}
value
}
///|
fn object_nonzero(data : Bytes, offset : Int32, width : Int32) -> Bool {
for n = offset; n < offset + width; n = n + 1 {
if data[n] != b'\x00' {
return true
}
}
false
}
///|
fn object_charge_symbols(
budget : ObjectBudget,
count : Int32,
offset : Int32,
) -> Unit raise InvalidObject {
if count > budget.symbols_left {
object_reject("OB003", offset, "symbol-table entry limit exceeded")
}
budget.symbols_left -= count
}
///|
fn object_string_table(
data : Bytes,
offset : Int32,
size : Int32,
elf : Bool,
) -> Unit raise InvalidObject {
object_range(data, offset, size)
if size > 0 &&
(data[offset + size - 1] != b'\x00' || (elf && data[offset] != b'\x00')) {
object_reject("OB001", offset, "invalid object string table terminator")
}
}
///|
fn object_name(
data : Bytes,
offset : Int32,
size : Int32,
index : Int32,
budget : ObjectBudget,
) -> Bytes raise InvalidObject {
if index < 0 || index >= size {
object_reject(
"OB001", offset, "symbol name index is outside its string table",
)
}
let start = offset + index
let mut end = start
while end < offset + size && data[end] != b'\x00' {
if end - start >= budget.names_left {
object_reject("OB003", start, "expanded symbol-name byte limit exceeded")
}
end += 1
}
if end == offset + size || end == start {
object_reject("OB001", start, "missing, empty or unterminated symbol name")
}
budget.names_left -= end - start
data[start:end].to_owned()
}
///|
priv struct ObjectSection {
kind : UInt32
offset : Int32
size : Int32
link : Int32
info : Int32
entry_size : Int32
header : Int32
}
///|
fn object_elf(
data : Bytes,
budget : ObjectBudget,
) -> Array[Bytes] raise InvalidObject {
object_range(data, 0, 16)
let wide = match data[4] {
b'\x01' => false
b'\x02' => true
_ => object_reject("OB002", 4, "unsupported ELF class")
}
let little = match data[5] {
b'\x01' => true
b'\x02' => false
_ => object_reject("OB001", 5, "invalid ELF byte order")
}
let width = if wide { 8 } else { 4 }
let header_size = if wide { 64 } else { 52 }
let section_size = if wide { 64 } else { 40 }
object_range(data, 0, header_size)
if data[6] != b'\x01' || object_number(data, 20, 4, little, 2147483647) != 1 {
object_reject("OB002", 6, "unsupported ELF version")
}
if object_number(data, 16, 2, little, 65535) != 1 {
object_reject(
"OB002", 16, "symbol discovery requires a relocatable ELF object",
)
}
let declared_header = object_number(
data,
if wide {
52
} else {
40
},
2,
little,
data.length(),
)
if declared_header < header_size {
object_reject("OB001", 0, "ELF header is smaller than its class requires")
}
let shoff = object_number(
data,
if wide {
40
} else {
32
},
width,
little,
data.length(),
)
let shstride = object_number(
data,
if wide {
58
} else {
46
},
2,
little,
65535,
)
let raw_count = object_number(
data,
if wide {
60
} else {
48
},
2,
little,
65535,
)
let raw_strings = object_number(
data,
if wide {
62
} else {
50
},
2,
little,
65535,
)
if raw_count >= 65280 || (raw_strings >= 65280 && raw_strings != 65535) {
object_reject(
"OB001", 0, "ELF reserved section count requires an extended encoding",
)
}
if shoff == 0 {
if raw_count != 0 || raw_strings != 0 {
object_reject("OB001", 0, "ELF section counts lack a section table")
}
return []
}
if shstride < section_size {
object_reject("OB001", shoff, "ELF section-header stride is too small")
}
object_range(data, shoff, shstride)
if object_tag(data, shoff + 4, little) != 0U {
object_reject("OB001", shoff, "ELF section zero must have SHT_NULL type")
}
let section_count = if raw_count == 0 {
object_number(
data,
shoff + (if wide { 32 } else { 20 }),
width,
little,
(data.length() - shoff) / shstride,
)
} else {
raw_count
}
if section_count == 0 || section_count > (data.length() - shoff) / shstride {
object_reject("OB001", shoff, "ELF section table is truncated")
}
let strings_index = if raw_strings == 65535 {
object_number(
data,
shoff + (if wide { 40 } else { 24 }),
4,
little,
section_count - 1,
)
} else {
raw_strings
}
if strings_index >= section_count {
object_reject("OB001", shoff, "ELF section-name table index is invalid")
}
let sections : Array[ObjectSection] = []
let companions : Map[Int32, Int32] = Map([])
for n = 0; n < section_count; n = n + 1 {
let header = shoff + n * shstride
let kind = object_tag(data, header + 4, little)
let relevant = kind == 2U || kind == 3U || kind == 18U
let (offset, size) = if kind == 0U || kind == 8U {
(0, 0)
} else {
let offset = object_number(
data,
header + (if wide { 24 } else { 16 }),
width,
little,
data.length(),
)
let size = object_number(
data,
header + (if wide { 32 } else { 20 }),
width,
little,
data.length() - offset,
)
(offset, size)
}
let link = if relevant {
object_number(
data,
header + (if wide { 40 } else { 24 }),
4,
little,
section_count - 1,
)
} else {
0
}
let info = if kind == 2U {
object_number(
data,
header + (if wide { 44 } else { 28 }),
4,
little,
2147483647,
)
} else {
0
}
let entry_size = if kind == 2U || kind == 18U {
object_number(
data,
header + (if wide { 56 } else { 36 }),
width,
little,
data.length(),
)
} else {
0
}
sections.push({ kind, offset, size, link, info, entry_size, header, })
if kind == 18U {
if companions.get(link) is Some(_) {
object_reject(
"OB001", header, "multiple extended-index tables for one symbol table",
)
}
companions[link] = n
}
}
if strings_index != 0 && sections[strings_index].kind != 3U {
object_reject(
"OB001",
sections[strings_index].header,
"ELF section-name table must be SHT_STRTAB",
)
}
for section in sections {
if section.kind == 18U && sections[section.link].kind != 2U {
object_reject(
"OB001",
section.header,
"extended indexes must link to a symbol table",
)
}
}
let result : Array[Bytes] = []
let seen : Map[Bytes, Unit] = Map([])
for table_index, table in sections {
if table.kind != 2U {
continue
}
let symbol_size = if wide { 24 } else { 16 }
if table.entry_size < symbol_size || table.size % table.entry_size != 0 {
object_reject(
"OB001",
table.header,
"invalid ELF symbol-table entry size",
)
}
let count = table.size / table.entry_size
object_charge_symbols(budget, count, table.header)
if count == 0 || table.info == 0 || table.info > count {
object_reject(
"OB001",
table.header,
"ELF symbol table has no valid local/global boundary",
)
}
let strings = sections[table.link]
if strings.kind != 3U {
object_reject(
"OB001",
table.header,
"ELF symbol table must link to SHT_STRTAB",
)
}
object_string_table(data, strings.offset, strings.size, true)
let companion = companions
.get(table_index)
.map(fn(index) { sections[index] })
if companion is Some(extra) &&
(extra.entry_size != 4 || extra.size / 4 != count || extra.size % 4 != 0) {
object_reject(
"OB001",
extra.header,
"extended-index table does not match the symbol table",
)
}
for n = 0; n < count; n = n + 1 {
let pos = table.offset + n * table.entry_size
if n == 0 && object_nonzero(data, pos, symbol_size) {
object_reject("OB001", pos, "ELF null symbol must contain zero fields")
}
let name_index = object_number(data, pos, 4, little, strings.size)
if name_index != 0 && name_index >= strings.size {
object_reject(
"OB001", pos, "ELF symbol name index is outside its string table",
)
}
let info = data[pos + (if wide { 4 } else { 12 })].to_int()
let binding = info >> 4
let kind = info & 15
if (n < table.info) != (binding == 0) {
object_reject(
"OB001", pos, "ELF local symbols do not precede external symbols",
)
}
let raw_section = object_number(
data,
pos + (if wide { 6 } else { 14 }),
2,
little,
65535,
)
let extra_index = match companion {
Some(extra) =>
object_number(
data,
extra.offset + n * 4,
4,
little,
section_count - 1,
)
None => 0
}
let section_index = if raw_section == 65535 {
if companion is None || extra_index == 0 {
object_reject(
"OB001", pos, "ELF SHN_XINDEX lacks a valid extended section index",
)
}
extra_index
} else {
if extra_index != 0 {
object_reject(
"OB001", pos, "unused ELF extended section index must be zero",
)
}
if raw_section < 65280 && raw_section >= section_count {
object_reject("OB001", pos, "ELF symbol refers to a missing section")
}
raw_section
}
if section_index > 0 &&
(raw_section < 65280 || raw_section == 65535) &&
sections[section_index].kind == 0U {
object_reject("OB001", pos, "ELF symbol refers to an inactive section")
}
if binding == 0 ||
section_index == 0 ||
kind == 3 ||
kind == 4 ||
name_index == 0 {
continue
}
if binding != 1 && binding != 2 && binding != 10 {
object_reject("OB002", pos, "unsupported ELF external symbol binding")
}
if raw_section >= 65280 &&
raw_section != 65535 &&
raw_section != 65521 &&
raw_section != 65522 {
object_reject("OB002", pos, "unsupported ELF special section index")
}
let name = object_name(
data,
strings.offset,
strings.size,
name_index,
budget,
)
if seen.get(name) is None {
seen[name] = ()
result.push(name)
}
}
}
result
}
///|
fn object_macho(
data : Bytes,
wide : Bool,
little : Bool,
budget : ObjectBudget,
) -> Array[Bytes] raise InvalidObject {
let header_size = if wide { 32 } else { 28 }
object_range(data, 0, header_size)
if object_number(data, 12, 4, little, 2147483647) != 1 {
object_reject(
"OB002", 12, "symbol discovery requires a Mach-O MH_OBJECT file",
)
}
let command_size = object_number(
data,
20,
4,
little,
data.length() - header_size,
)
let commands = object_number(data, 16, 4, little, command_size / 8)
let commands_end = header_size + command_size
let mut pos = header_size
let mut symtab : (Int32, Int32, Int32, Int32)? = None
let sections_no_toc : Array[Bool] = []
for command_index = 0
command_index < commands
command_index = command_index + 1 {
if commands_end - pos < 8 {
object_reject("OB001", pos, "truncated Mach-O load command")
}
let command = object_tag(data, pos, little)
let size = object_number(data, pos + 4, 4, little, commands_end - pos)
if size < 8 || size % (if wide { 8 } else { 4 }) != 0 {
object_reject("OB001", pos, "invalid Mach-O load-command size")
}
if command == 2U {
if size != 24 || symtab is Some(_) {
object_reject("OB001", pos, "invalid or duplicate Mach-O LC_SYMTAB")
}
let symbol_offset = object_number(data, pos + 8, 4, little, data.length())
let symbol_count = object_number(
data,
pos + 12,
4,
little,
(data.length() - symbol_offset) / (if wide { 16 } else { 12 }),
)
let string_offset = object_number(
data,
pos + 16,
4,
little,
data.length(),
)
let string_size = object_number(
data,
pos + 20,
4,
little,
data.length() - string_offset,
)
symtab = Some((symbol_offset, symbol_count, string_offset, string_size))
} else if command == 1U || command == 25U {
if (command == 25U) != wide {
object_reject(
"OB001", pos, "Mach-O segment width differs from its header",
)
}
let base = if wide { 72 } else { 56 }
let stride = if wide { 80 } else { 68 }
if size < base {
object_reject("OB001", pos, "truncated Mach-O segment command")
}
let count = object_number(
data,
pos + (if wide { 64 } else { 48 }),
4,
little,
(size - base) / stride,
)
if size != base + count * stride {
object_reject(
"OB001", pos, "Mach-O segment section count does not match command size",
)
}
let file_offset = object_number(
data,
pos + (if wide { 40 } else { 32 }),
if wide {
8
} else {
4
},
little,
data.length(),
)
ignore(
object_number(
data,
pos + (if wide { 48 } else { 36 }),
if wide {
8
} else {
4
},
little,
data.length() - file_offset,
),
)
for n = 0; n < count; n = n + 1 {
let section = pos + base + n * stride
let flags = object_tag(
data,
section + (if wide { 64 } else { 56 }),
little,
)
let kind = flags & 255U
sections_no_toc.push((flags & 0x40000000U) != 0U)
if kind != 1U && kind != 12U && kind != 18U {
let offset = object_number(
data,
section + (if wide { 48 } else { 40 }),
4,
little,
data.length(),
)
ignore(
object_number(
data,
section + (if wide { 40 } else { 36 }),
if wide {
8
} else {
4
},
little,
data.length() - offset,
),
)
}
let relocation_offset = object_number(
data,
section + (if wide { 56 } else { 48 }),
4,
little,
data.length(),
)
ignore(
object_number(
data,
section + (if wide { 60 } else { 52 }),
4,
little,
(data.length() - relocation_offset) / 8,
),
)
}
}
pos += size
}
if pos != commands_end {
object_reject("OB001", pos, "Mach-O load commands do not fill sizeofcmds")
}
guard symtab is Some((symbol_offset, count, string_offset, string_size)) else {
return []
}
object_charge_symbols(budget, count, symbol_offset)
object_string_table(data, string_offset, string_size, false)
let result : Array[Bytes] = []
let seen : Map[Bytes, Unit] = Map([])
let stride = if wide { 16 } else { 12 }
for n = 0; n < count; n = n + 1 {
let symbol = symbol_offset + n * stride
let name_index = object_number(data, symbol, 4, little, string_size)
if name_index != 0 && name_index >= string_size {
object_reject(
"OB001", symbol, "Mach-O symbol name index is outside its string table",
)
}
let flags = data[symbol + 4].to_int()
if (flags & 224) != 0 {
continue
}
let kind = flags & 14
let section = data[symbol + 5].to_int()
if kind == 14 {
if section == 0 || section > sections_no_toc.length() {
object_reject(
"OB001", symbol, "Mach-O symbol refers to a missing section",
)
}
} else if section != 0 {
object_reject(
"OB001", symbol, "Mach-O non-section symbol must use NO_SECT",
)
}
if (flags & 1) == 0 || name_index == 0 {
continue
}
if (
kind == 0 && !object_nonzero(data, symbol + 8, if wide { 8 } else { 4 })
) ||
kind == 12 ||
(kind == 14 && sections_no_toc[section - 1]) {
continue
}
if kind != 0 && kind != 2 && kind != 10 && kind != 14 {
object_reject("OB002", symbol, "unsupported Mach-O external symbol type")
}
if kind == 10 {
let target = object_number(
data,
symbol + 8,
if wide {
8
} else {
4
},
little,
string_size,
)
if target == 0 ||
target >= string_size ||
data[string_offset + target] == b'\x00' {
object_reject(
"OB001",
symbol + 8,
"invalid Mach-O indirect symbol target",
)
}
}
let name = object_name(data, string_offset, string_size, name_index, budget)
if seen.get(name) is None {
seen[name] = ()
result.push(name)
}
}
result
}
///|
fn object_coff_machine(machine : Int32) -> Bool {
match machine {
0x014c
| 0x8664
| 0x01c0
| 0x01c2
| 0x01c4
| 0xaa64
| 0x0200
| 0x5032
| 0x5064
| 0x5128
| 0x6232
| 0x6264
| 0x0184
| 0x0284
| 0x01d3
| 0x0ebc
| 0x9041
| 0x0266
| 0x0366
| 0x0466
| 0x01f0
| 0x01f1
| 0x0162
| 0x0166
| 0x0168
| 0x0169
| 0x01a2
| 0x01a3
| 0x01a6
| 0x01a8 => true
_ => false
}
}
///|
fn object_coff(
data : Bytes,
budget : ObjectBudget,
big~ : Bool = false,
) -> Array[Bytes] raise InvalidObject {
let header_size = if big { 56 } else { 20 }
let symbol_size = if big { 20 } else { 18 }
let storage_offset = if big { 18 } else { 16 }
let table_field = if big { 48 } else { 8 }
let count_field = if big { 52 } else { 12 }
object_range(data, 0, header_size)
if !big {
let optional_size = object_number(data, 16, 2, true, 65535)
let characteristics = object_number(data, 18, 2, true, 65535)
if optional_size != 0 || (characteristics & 0x2002) != 0 {
object_reject(
"OB002", 16, "symbol discovery requires a relocatable COFF object",
)
}
}
let section_count = object_number(
data,
if big {
44
} else {
2
},
if big {
4
} else {
2
},
true,
(data.length() - header_size) / 40,
)
if !big && section_count > 65279 {
object_reject("OB002", 2, "COFF section count requires the bigobj format")
}
let section_end = header_size + section_count * 40
for n = 0; n < section_count; n = n + 1 {
let section = header_size + n * 40
let flags = object_tag(data, section + 36, true)
let raw_offset = object_number(data, section + 20, 4, true, data.length())
if raw_offset != 0 || (flags & 128U) == 0U {
let raw_size = object_number(
data,
section + 16,
4,
true,
data.length() - raw_offset,
)
if raw_size > 0 && raw_offset < section_end {
object_reject(
"OB001", section, "COFF section data overlaps the file headers",
)
}
}
let relocation_offset = object_number(
data,
section + 24,
4,
true,
data.length(),
)
let relocations = object_number(data, section + 32, 2, true, 65535)
let relocation_count = if (flags & 0x01000000U) != 0U {
if relocations != 65535 || relocation_offset < section_end {
object_reject(
"OB001", section, "invalid COFF extended relocation count",
)
}
let count = object_number(
data,
relocation_offset,
4,
true,
(data.length() - relocation_offset) / 10,
)
if count <= 65535 {
object_reject(
"OB001", relocation_offset, "COFF relocation overflow marker is unnecessary",
)
}
count
} else {
relocations
}
if relocation_count > (data.length() - relocation_offset) / 10 ||
(relocation_count > 0 && relocation_offset < section_end) {
object_reject(
"OB001", section, "COFF relocation records extend outside the object",
)
}
let lines_offset = object_number(data, section + 28, 4, true, data.length())
let lines = object_number(data, section + 34, 2, true, 65535)
if lines > (data.length() - lines_offset) / 6 ||
(lines > 0 && lines_offset < section_end) {
object_reject(
"OB001", section, "COFF line records extend outside the object",
)
}
}
let table_offset = object_number(data, table_field, 4, true, data.length())
let count = object_number(
data,
count_field,
4,
true,
(data.length() - table_offset) / symbol_size,
)
object_charge_symbols(budget, count, count_field)
if table_offset == 0 {
if count != 0 {
object_reject(
"OB001", table_field, "COFF symbols lack a symbol-table offset",
)
}
return []
}
if table_offset < section_end {
object_reject(
"OB001", table_field, "COFF symbol table overlaps the file headers",
)
}
let strings_offset = table_offset + count * symbol_size
let strings_size = object_number(
data,
strings_offset,
4,
true,
data.length() - strings_offset,
)
if strings_size < 4 {
object_reject(
"OB001", strings_offset, "COFF string table is smaller than its size field",
)
}
if strings_size > 4 {
object_string_table(data, strings_offset, strings_size, false)
}
let result : Array[Bytes] = []
let seen : Map[Bytes, Unit] = Map([])
let primary : Map[Int32, Unit] = Map([])
let weak_targets : Array[(Int32, Int32)] = []
let mut index = 0
while index < count {
primary[index] = ()
let pos = table_offset + index * symbol_size
let auxiliary_count = data[pos + storage_offset + 1].to_int()
if auxiliary_count > count - index - 1 {
object_reject(
"OB001",
pos + storage_offset + 1,
"COFF auxiliary records exceed the symbol table",
)
}
let section_tag = if big {
object_tag(data, pos + 12, true)
} else {
object_number(data, pos + 12, 2, true, 65535).reinterpret_as_uint()
}
let section = if section_tag == (if big { 0xffffffffU } else { 65535U }) {
-1
} else if section_tag == (if big { 0xfffffffeU } else { 65534U }) {
-2
} else {
let number = object_number(
data,
pos + 12,
if big {
4
} else {
2
},
true,
section_count,
)
if !big && number > 65279 {
object_reject("OB001", pos + 12, "reserved COFF section number")
}
number
}
let storage = data[pos + storage_offset].to_int()
let common = section == 0 && object_nonzero(data, pos + 8, 4)
let mut exported = (storage == 2 || storage == 5) &&
(common || (section != 0 && section != -2))
if storage == 105 {
if auxiliary_count != 1 || section != 0 || common {
object_reject("OB001", pos, "invalid COFF weak-external symbol record")
}
let target = object_number(data, pos + symbol_size, 4, true, count - 1)
weak_targets.push((target, pos + symbol_size))
let search = object_number(
data,
pos + symbol_size + 4,
4,
true,
2147483647,
)
if search < 1 || search > 4 {
object_reject(
"OB002",
pos + symbol_size + 4,
"unsupported COFF weak-external search mode",
)
}
exported = search == 3
}
let name = if !object_nonzero(data, pos, 4) {
let name_index = object_number(data, pos + 4, 4, true, strings_size)
if name_index < 4 || name_index >= strings_size {
object_reject(
"OB001",
pos + 4,
"COFF symbol name index is outside its string table",
)
}
if exported {
Some(
object_name(data, strings_offset, strings_size, name_index, budget),
)
} else {
None
}
} else if exported {
let mut length = 0
while length < 8 && data[pos + length] != b'\x00' {
length += 1
}
if length == 0 {
object_reject("OB001", pos, "empty COFF external symbol name")
}
if length > budget.names_left {
object_reject("OB003", pos, "expanded symbol-name byte limit exceeded")
}
budget.names_left -= length
Some(data[pos:pos + length].to_owned())
} else {
None
}
if name is Some(name) && seen.get(name) is None {
seen[name] = ()
result.push(name)
}
index += 1 + auxiliary_count
}
for (target, pos) in weak_targets {
if primary.get(target) is None {
object_reject(
"OB001", pos, "COFF weak target refers to an auxiliary record",
)
}
let storage = data[table_offset + target * symbol_size + storage_offset].to_int()
if storage != 2 && storage != 5 && storage != 105 {
object_reject(
"OB001", pos, "COFF weak target must refer to an external symbol",
)
}
}
result
}
///|
fn object_discover(
data : Bytes,
budget : ObjectBudget,
) -> Array[Bytes] raise InvalidObject {
if data.length() < 4 {
if data.length() > 0 && data[0] == b'\x7f' {
object_reject("OB001", 0, "truncated ELF magic")
}
return []
}
let magic = data[:4]
if magic == b"\x7fELF"[:] {
return object_native_definitions(object_elf(data, budget))
}
if magic == b"\xce\xfa\xed\xfe"[:] {
return object_native_definitions(object_macho(data, false, true, budget))
}
if magic == b"\xcf\xfa\xed\xfe"[:] {
return object_native_definitions(object_macho(data, true, true, budget))
}
if magic == b"\xfe\xed\xfa\xce"[:] {
return object_native_definitions(object_macho(data, false, false, budget))
}
if magic == b"\xfe\xed\xfa\xcf"[:] {
return object_native_definitions(object_macho(data, true, false, budget))
}
if magic == b"\x00\x00\xff\xff"[:] {
return object_coff_anonymous(data, budget)
}
if magic == b"BC\xc0\xde"[:] || magic == b"\xde\xc0\x17\x0b"[:] {
return object_bitcode(data, budget)
}
let coff_machine = data[0].to_int() + data[1].to_int() * 256
if object_coff_machine(coff_machine) {
return object_native_definitions(object_coff(data, budget))
}
if magic == b"\xca\xfe\xba\xbe"[:] ||
magic == b"\xbe\xba\xfe\xca"[:] ||
magic == b"\xca\xfe\xba\xbf"[:] ||
magic == b"\xbf\xba\xfe\xca"[:] ||
magic == b"\x00asm"[:] ||
coff_machine == 0x0160 ||
coff_machine == 0xa641 ||
coff_machine == 0xa64e ||
data[:2] == b"MZ"[:] ||
(
data.length() >= 8 &&
(data[:8] == b"!\n"[:] || data[:8] == b"!\n"[:])
) {
object_reject(
"OB002", 0, "object container or format is not supported for symbol discovery",
)
}
[]
}
///|
fn object_native_definitions(
names : Array[Bytes],
) -> Array[Bytes] raise InvalidObject {
if names.contains(b"__gnu_lto_slim") || names.contains(b"___gnu_lto_slim") {
object_reject(
"OB002", 0, "GCC slim LTO requires plugin symbol discovery; use a fat object or explicit symbol mappings",
)
}
names
}
///|
/// Discover archive-index names from relocatable ELF, Mach-O and COFF objects.
/// ELF32/64 and Mach-O32/64 accept both byte orders. Defined external, weak,
/// common and absolute symbols are included; undefined/local/debug symbols
/// are omitted. Duplicate names within one object retain their first position.
/// Standard little-endian COFF includes weak aliases and skips auxiliary records.
/// Bigobj, short import objects and LLVM bitcode with an embedded version-3
/// symbol table are supported. Bitcode wrappers and module groups are decoded
/// without constructing IR. Executable images and universal objects are rejected.
/// GCC slim LTO requires explicit mappings or plugin symbol discovery.
/// Ordinary data returns an empty array; recognized unsupported object formats
/// and malformed supported objects return an error.
/// max_symbols counts inspected symbol-table entries, including filtered entries.
/// max_symbol_name_bytes counts expanded discovered names before deduplication.
pub fn symbols_from_object(
data : Bytes,
max_symbols~ : Int32 = 1000000,
max_symbol_name_bytes~ : Int32 = 67108864,
) -> Result[Array[Bytes], ObjectError] {
if max_symbols < 0 || max_symbol_name_bytes < 0 {
return Err({
code: "OB003",
offset: 0,
message: "object discovery limits must not be negative",
})
}
Ok(
object_discover(data, {
symbols_left: max_symbols,
names_left: max_symbol_name_bytes,
}),
) catch {
InvalidObject(error) => Err(error)
}
}
///|
/// Discover fresh symbol targets after editing. Budgets are shared by all
/// members. WR006 includes the failing member position and object error offset.
pub fn ArchiveBuilder::discover_symbols(
self : ArchiveBuilder,
max_symbols~ : Int32 = 1000000,
max_symbol_name_bytes~ : Int32 = 67108864,
) -> Result[Array[WriteSymbol], WriteError] {
if max_symbols < 0 || max_symbol_name_bytes < 0 {
return Err({
code: "WR003",
member_index: None,
message: "object discovery limits must not be negative",
})
}
let budget : ObjectBudget = {
symbols_left: max_symbols,
names_left: max_symbol_name_bytes,
}
let result : Array[WriteSymbol] = []
for member_index, entry in self.members {
let names = object_discover(entry.data, budget) catch {
InvalidObject(error) =>
return Err({
code: if error.code == "OB003" {
"WR003"
} else {
"WR006"
},
member_index: Some(member_index),
message: "\{error.code} at object byte \{error.offset}: \{error.message}",
})
}
for name in names {
result.push({ name, member_index, })
}
}
Ok(result)
}