IRX Files INTRODUCTION ------------- The heart of the whole PS2 is its IO processor (PS1). It works by a lot of chained modules, that inter-communicate themselves and some of these modules provide communication to the EE (rpc). Well, I have figured out how the modules works, how do they load, how do they communicate with kernel, and how do they provide functions to other iop modules. IRX MODULE FILE ---------------- First of all, an .irx file is a modified version of a regular elf object file. The difference is this file has executable extensions but also has its relocation table without its symbol table. (read below and you'll understand that) When the kernel loads up a module, it seeks for a specific section called ".iopmod" (section type 0x70000080), which has all the info iop wants. This section is a structure like this: (to better understand this, you should manage how an elf file works) offset type description ------------------------------------------------------------------------ 0x0 addr32 pointer to a "module" structure this "module" structure is something like: u16 attribute (is it version?) char * module_name 0x4 addr32 start address (the same as the elf is) 0x8 addr32 this is some kind of "heap" start. this value is calculated by adding 0x7ff0 + sizeof(data section) + sizeof(text section) 0xc u32 text_section size 0x10 u32 data_section size 0x14 u32 bss_section size 0x18 u16 module_structure attribute 0x1a char * module_name The module process of loading is based on the .irx organization. When a .irx is generated the file block is arranged like this: | CODE BLOCK | DATA BLOCK | BSS BLOCK | There is a reason for that organization. When the kernel loads up the iopmod section, it sums the text_size + data_size + bss_size. It allocates this ammount of memory inside iop memory. After that, it moves all data to this allocated segment, and (the trickiest part) loads the relocation sections. It just sums the relocations (depending of each type, of course) by the base iop allocated memory address. This is the reason it doesn't need the symbol table, because it has been previously "used". Now it just executes the "start address" and everything run. LIBRARIANS, INTER_MODULE_COMMUNICATION --------------------------------------- IOP has a small memory size (2M). If you manage to include the standard libs or something like that for each module, you would probably have a few modules running, not dozens of it. Sony has used some very good tricks for its inter_module_communication. You might be asking yourself, why didn't sony used dynamic linking. The answer is quiet easy: first of all, sony didn't want everyone watching around modules symbol tables (I wouldn't) and the kernel implementation would get more complex and I guess they didn't want to spend memory to do that. To solve that point, they implemented some "zero register exception handler" that will call the apropriate function. To better understand that, I'll paste some .irx file I've found out there, containing debug information. (You should look for them too... many, many games always have something good). HOW TO IMPORT FUNCTIONS ------------------------ 000000000000e570 : e570: 41e00000 0x41e00000 < MAGIC NUMBER e574: 00000000 nop < ALWAYS ZERO e578: 00000104 0x104 < VERSION? e57c: 7362696c 0x7362696c < "libsd" e580: 00000064 0x64 ^ 000000000000e584 : < function references e584: 03e00008 jr $ra e588: 24000004 li $zero,4 When an exception is generated, it goes backwards the memory until it finds the '0x41e00000'. After that, it looks for the library reference name "libsd" and the kernel already knows the function reference name. With this information, it's easy to write a simple program to find out import references (and export too). HOW TO EXPORT FUNCTIONS ------------------------ This method is quite similar to the importation one. What changes is the magic number and the functions references. The 'stub' is the same (the magic number for export is 0x41c00000. The functions reference is an array of addresses finished with a NULL entry. Only by these magic numbers, is totally easy to track down kernel functions, describing which functions are importable and exportable and so on. It's possible to start reconstructing the symbol table of a symbol-less table module, only by cross referencing the functions. You might look further inside the kernel modules, they are all irx modules. You can disassemble each one of modules function and see exactly how the kernel work.