Skip to content

Instantly share code, notes, and snippets.

@EllipticEllipsis
Last active May 11, 2023 11:08
Show Gist options
  • Select an option

  • Save EllipticEllipsis/0bf5facc7c2eec9073e902d8f65b002e to your computer and use it in GitHub Desktop.

Select an option

Save EllipticEllipsis/0bf5facc7c2eec9073e902d8f65b002e to your computer and use it in GitHub Desktop.
Program to read and print the rtproc table sometimes included in IDO ELF files (e.g. as0, as1, nld).
#!/usr/bin/env python3
# Program to read and print the rtproc table sometimes included in IDO ELF files (e.g. as0, as1, nld).
#
# Section is divided into main table and string table. Entries in the main table have this format (from sym.h):
#
# /*
# * The structure of the runtime procedure descriptor created by the loader
# * for use by the static exception system.
# */
# typedef struct runtime_pdr {
# /* 0x00 */ ulong_i adr; /* memory address of start of procedure */
# /* 0x04 */ long_i regmask; /* save register mask */
# /* 0x08 */ long_i regoffset; /* save register offset */
# /* 0x0C */ long_i fregmask; /* save floating point register mask */
# /* 0x10 */ long_i fregoffset; /* save floating point register offset */
# /* 0x14 */ long_i frameoffset; /* frame size */
# /* 0x18 */ short framereg; /* frame pointer register */
# /* 0x1A */ short pcreg; /* offset or reg of return pc */
# /* 0x1C */ long_i irpss; /* index into the runtime string table */
# /* 0x20 */ long_i reserved;
# /* 0x24 */ struct exception_info *exception_info;/* pointer to exception array */
# } RPDR, *pRPDR; /* size = 0x28 */
# #define cbRPDR sizeof(RPDR)
# #define rpdNil ((pRPDR) 0)
# #define rsdNil ((pSYMR) 0)
#
# To this we may add our own explanations:
# - regmask is a list of the registers the function disturbs that need to be saved to the stack, packed one bit per
# register into a word. In practice under an ABI this means
# - s registers
# - gp (in o32)
# - s8/fp
# - ra
# Most of these are the call-preserved/callee-saved/non-volatile ones in the ABI, but ra is included too.
# - regoffset appears to be the offset from the top of the function stack frame where the first of these is saved.
# - Similarly fregmask and fregoffset, although this just covers the fs registers.
# - frameoffset is the magnitude of the distance sp is moved in the function (prologue only?)
# - framereg is the number of the register that holds the function frame address, unually sp (or fp?)
# - pcreg is essentially always ra
#
# The main table begins with an entry that is all zeros and ends with an entry with address 0xFFFFFFFF.
# The string table follows, aligned to the next 8 boundary. Offsets are measured from the start of the string table.
# The format of the string table is essentially the same as an ELF strtab, nul-terminated strings one after another.
#
# N.B. We cannot just read the section table to find offset, size etc. because some ELFs include it at the end of the
# .data section (IDO 5.3 can do this, for example)
import argparse
import sys
import struct
# o32
GPR_NAMES = [
"zero",
"at",
"v0",
"v1",
"a0",
"a1",
"a2",
"a3",
"t0",
"t1",
"t2",
"t3",
"t4",
"t5",
"t6",
"t7",
"s0",
"s1",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"t8",
"t9",
"k0",
"k1",
"gp",
"sp",
"fp", # or s8
"ra",
]
# o32
FPR_NAMES = [
"$fv0",
"$fv0f",
"$fv1",
"$fv1f",
"$ft0",
"$ft0f",
"$ft1",
"$ft1f",
"$ft2",
"$ft2f",
"$ft3",
"$ft3f",
"$fa0",
"$fa0f",
"$fa1",
"$fa1f",
"$ft4",
"$ft4f",
"$ft5",
"$ft5f",
"$fs0",
"$fs0f",
"$fs1",
"$fs1f",
"$fs2",
"$fs2f",
"$fs3",
"$fs3f",
"$fs4",
"$fs4f",
"$fs5",
"$fs5f",
]
cbRPDR = 0x28
def regmask_to_registers(regmask: int, names: list) -> str:
i = 0
reg_list = []
while regmask != 0:
if regmask & 1:
reg_list.append(names[i])
regmask >>= 1
i += 1
# zero, at, ...
reg_list.reverse() # ra, fp, sp, gp, ...
if len(reg_list) > 0:
return f"<{'|'.join(reg_list)}>"
else:
return ""
def print_rtproc(
file: bytes,
start: int
# , strings_start: int, end: int
):
# Left in in case we have to deal with some weird elephant-in-Cairoless case.
# length = strings_start - start
# print(f"Total length: 0x{length:X}")
# length = (length // cbRPDR) * cbRPDR
# print(f"Floored length: 0x{length:X}")
# table = struct.iter_unpack(">IIIIIIHHIII", file[start:start+length])
# extract table
table = list()
off = start
while True:
entry = struct.unpack_from(">IIiIiIHHIII", file[off:])
table.append(entry)
off += cbRPDR
if entry[0] == 0xFFFFFFFF:
# print(f"Final entry at {off:X}")
break
# Align for start of string table
strings_start = (off + 8 - 1) // 8 * 8
# extract strings
# strings = file[strings_start:]
# split_strings = strings.split(b'\x00')
# print(split_strings)
strings_dict = dict({0: ""})
# There's probably a better way to do this involving split, but we'd have to find the end anyway
cur_off = strings_start + 1
prev_off = strings_start + 1
nul = False
while True:
cur_off += 1
if file[cur_off] == 0:
if nul:
break
nul = True
strings_dict[prev_off - strings_start] = file[prev_off:cur_off].decode()
prev_off = cur_off + 1
else:
nul = False
# If you just want the names of the functions, printing this is sufficient
# print(strings_dict)
# return
# Column headings
print(
"adr, "
"regmask, "
"regoffset, "
"fregmask, "
"fregoffset, "
"frameoffset, "
"framereg, "
"pcreg, "
"irpss, "
"reserved, "
"exception_info, "
"regmask_dis, "
"fregmask_dis, "
"name, "
)
# print whole table
for entry in table:
(
adr,
regmask,
regoffset,
fregmask,
fregoffset,
frameoffset,
framereg,
pcreg,
irpss,
reserved,
exception_info,
) = entry
disp_regoffset = f"-0x{-regoffset:X}" if regoffset < 0 else f"0x{regoffset:X}"
disp_fregoffset = (
f"-0x{-fregoffset:X}" if fregoffset < 0 else f"0x{fregoffset:X}"
)
disp_frameoffset = f"0x{frameoffset:X}"
disp_irpss = f"0x{irpss:X}"
disp_exception_info = "NULL" if exception_info == 0 else f"0x{exception_info:X}"
print(
f"0x{adr:08X}, "
f"0b{regmask:032b}, "
f"{disp_regoffset:>8}, "
f"0b{fregmask:032b}, "
f"{disp_fregoffset:>8}, "
f"{disp_frameoffset:>8}, "
f"{GPR_NAMES[framereg]:4}, "
f"{GPR_NAMES[pcreg]:4}, "
f"{disp_irpss:>8}, "
f"0x{reserved:08X}, "
f"{disp_exception_info:>8}, ",
end="",
)
# unformatted version
# print(f"0x{adr:08X} 0x{regmask:08X} 0x{regoffset:08X} 0x{fregmask:08X} 0x{fregoffset:08X} 0x{frameoffset:08X} 0x{framereg:04X} 0x{pcreg:04X} 0x{irpss:08X} 0x{reserved:08X} 0x{exception_info:08X} ", end="")
# Print regmask analysis
print(f"{regmask_to_registers(regmask, GPR_NAMES)}, {regmask_to_registers(fregmask, FPR_NAMES)}, ", end="")
# Print name
str_offset = irpss
print(strings_dict[str_offset])
def main():
parser = argparse.ArgumentParser()
parser.add_argument("file")
parser.add_argument("rtproc_offset")
# parser.add_argument("strings_offset", help="into rt")
# parser.add_argument("end")
args = parser.parse_args()
with open(args.file, "rb") as f:
f_contents = f.read()
start = int(args.rtproc_offset, 16)
# strings_start = int(args.strings_offset, 16)
# end = int(args.end, 16)
print_rtproc(
f_contents,
start
# , strings_start, end
)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment