// SPDX-License-Identifier: Apache-2.0
///|
pub(all) struct CoffSection {
name : String
virtual_size : Int32
virtual_address : UInt64
raw_size : Int32
raw_offset : Int32
relocation_offset : Int32
line_number_offset : Int32
relocation_count : Int32
line_number_count : Int32
characteristics : UInt64
}
///|
pub(all) struct CoffSymbol {
name : String
value : UInt64
section_number : Int32
symbol_type : Int32
storage_class : Int32
}
///|
pub(all) struct CoffObject {
machine : Int32
section_count : Int32
timestamp : Int32
symbol_table_pointer : Int32
symbol_count : Int32
optional_header_size : Int32
characteristics : Int32
sections : Array[CoffSection]
symbols : Array[CoffSymbol]
}
///|
pub fn CoffObject::parse(data : Bytes) -> Self raise PeError {
if data.length() < 20 {
raise PeError(0, "file is too small to contain a COFF header")
}
let machine = read_u16(data, 0)
let section_count = read_u16(data, 2)
if section_count > 4096 {
raise PeError(2, "COFF section count exceeds the safety limit")
}
let timestamp = checked_int(read_u32(data, 4), 4, "COFF timestamp")
let symbol_table_pointer = checked_int(
read_u32(data, 8),
8,
"COFF symbol-table pointer",
)
let symbol_count = checked_int(read_u32(data, 12), 12, "COFF symbol count")
let optional_header_size = read_u16(data, 16)
let characteristics = read_u16(data, 18)
let sections_offset = 20 + optional_header_size
ensure_range(data, sections_offset, section_count * 40)
let string_table_offset = if symbol_table_pointer > 0 && symbol_count > 0 {
Some(symbol_table_pointer + symbol_count * 18)
} else {
None
}
let sections = []
for index in 0..= 0 {
match string_table_offset {
Some(table_offset) => read_coff_string(data, table_offset, name_offset)
None => short_name
}
} else {
short_name
}
let virtual_size = checked_int(
read_u32(data, offset + 8),
offset + 8,
"COFF section virtual size",
)
let virtual_address = read_u32(data, offset + 12)
let raw_size = checked_int(
read_u32(data, offset + 16),
offset + 16,
"COFF section raw size",
)
let raw_offset = checked_int(
read_u32(data, offset + 20),
offset + 20,
"COFF section raw offset",
)
let relocation_offset = checked_int(
read_u32(data, offset + 24),
offset + 24,
"COFF relocation offset",
)
let line_number_offset = checked_int(
read_u32(data, offset + 28),
offset + 28,
"COFF line-number offset",
)
if raw_size > 0 {
ensure_range(data, raw_offset, raw_size)
}
sections.push({
name,
virtual_size,
virtual_address,
raw_size,
raw_offset,
relocation_offset,
line_number_offset,
relocation_count: read_u16(data, offset + 32),
line_number_count: read_u16(data, offset + 34),
characteristics: read_u32(data, offset + 36),
})
}
let symbols = if symbol_table_pointer > 0 && symbol_count > 0 {
read_coff_symbols(
data, symbol_table_pointer, symbol_count, string_table_offset,
)
} else {
[]
}
{
machine,
section_count,
timestamp,
symbol_table_pointer,
symbol_count,
optional_header_size,
characteristics,
sections,
symbols,
}
}
///|
fn decimal_tail(text : String) -> Int32 {
if !text.has_prefix("/") || text.length() < 2 {
return -1
}
let chars = text.to_array()
let mut value = 0
let mut index = 1
while index < chars.length() {
let char = chars[index]
if char < '0' || char > '9' {
return -1
}
value = value * 10 + (char.to_int() - '0'.to_int())
index = index + 1
}
value
}
///|
fn read_coff_symbols(
data : Bytes,
table_offset : Int32,
symbol_count : Int32,
string_table_offset : Int32?,
) -> Array[CoffSymbol] raise PeError {
ensure_range(data, table_offset, symbol_count * 18)
let symbols = []
let limit = if symbol_count < 65536 { symbol_count } else { 65536 }
let mut index = 0
while index < limit {
let offset = table_offset + index * 18
let raw_name = read_u32(data, offset)
let name = if raw_name == 0UL {
let string_offset = checked_int(
read_u32(data, offset + 4),
offset + 4,
"COFF symbol string offset",
)
match string_table_offset {
Some(table_offset) =>
read_coff_string(data, table_offset, string_offset)
None => ""
}
} else {
read_ascii_fixed(data, offset, 8)
}
let raw_section = read_u16(data, offset + 12)
let section_number = if raw_section >= 0x8000 {
raw_section - 0x10000
} else {
raw_section
}
symbols.push({
name,
value: read_u32(data, offset + 8),
section_number,
symbol_type: read_u16(data, offset + 14),
storage_class: read_u8(data, offset + 16),
})
let aux_count = read_u8(data, offset + 17)
index = index + 1 + aux_count
}
symbols
}
///|
fn read_coff_string(
data : Bytes,
table_offset : Int32,
string_offset : Int32,
) -> String raise PeError {
ensure_range(data, table_offset, 4)
let table_size = checked_int(
read_u32(data, table_offset),
table_offset,
"COFF string-table size",
)
if table_size < 4 {
raise PeError(table_offset, "invalid COFF string-table size")
}
ensure_range(data, table_offset, table_size)
if string_offset < 4 || string_offset >= table_size {
raise PeError(table_offset + string_offset, "invalid COFF string offset")
}
read_ascii_z(data, table_offset + string_offset, table_size - string_offset)
}