Last active
May 11, 2023 11:08
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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