Difference between revisions of "NSO"
Misson20000 (talk | contribs) (surprised the page didn't already mention that compression is LZ4) |
|||
Line 1: | Line 1: | ||
− | |||
NSO is the main executable format. | NSO is the main executable format. | ||
It starts with the "NSO" header and mainly describes .text, .rodata, and .data segments (like a short-form of ELF program headers). If the segments are compressed, they are compressed using LZ4. | It starts with the "NSO" header and mainly describes .text, .rodata, and .data segments (like a short-form of ELF program headers). If the segments are compressed, they are compressed using LZ4. | ||
− | = | + | = Header = |
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
|- | |- | ||
Line 17: | Line 16: | ||
| 0x4 | | 0x4 | ||
| 4 | | 4 | ||
− | | | + | | Version (always 0) |
|- | |- | ||
| 0x8 | | 0x8 | ||
| 4 | | 4 | ||
− | | Reserved | + | | Reserved |
|- | |- | ||
| 0xC | | 0xC | ||
| 4 | | 4 | ||
− | | Flags | + | | [[#Flags|Flags]] |
|- | |- | ||
| 0x10 | | 0x10 | ||
| 0xC | | 0xC | ||
− | | .text | + | | [[#SegmentHeader|SegmentHeader]] for .text |
|- | |- | ||
| 0x1C | | 0x1C | ||
| 0x4 | | 0x4 | ||
− | | | + | | ModuleNameOffset (calculated by sizeof(header)) |
|- | |- | ||
| 0x20 | | 0x20 | ||
| 0xC | | 0xC | ||
− | | .rodata | + | | [[#SegmentHeader|SegmentHeader]] for .rodata |
|- | |- | ||
| 0x2C | | 0x2C | ||
| 0x4 | | 0x4 | ||
− | | | + | | ModuleNameSize |
|- | |- | ||
| 0x30 | | 0x30 | ||
| 0xC | | 0xC | ||
− | | .data | + | | [[#SegmentHeader|SegmentHeader]] for .data |
|- | |- | ||
| 0x3C | | 0x3C | ||
| 0x4 | | 0x4 | ||
− | | | + | | BssSize |
|- | |- | ||
| 0x40 | | 0x40 | ||
| 0x20 | | 0x20 | ||
− | | | + | | [[#ModuleId|ModuleId]] |
|- | |- | ||
| 0x60 | | 0x60 | ||
| 0x4 | | 0x4 | ||
− | | .text compressed size | + | | TextFileSize (.text compressed size) |
|- | |- | ||
| 0x64 | | 0x64 | ||
| 0x4 | | 0x4 | ||
− | | .rodata compressed size | + | | RoFileSize (.rodata compressed size) |
|- | |- | ||
| 0x68 | | 0x68 | ||
| 0x4 | | 0x4 | ||
− | | .data compressed size | + | | DataFileSize (.data compressed size) |
|- | |- | ||
| 0x6C | | 0x6C | ||
| 0x1C | | 0x1C | ||
− | | Reserved | + | | Reserved |
|- | |- | ||
| 0x88 | | 0x88 | ||
| 0x8 | | 0x8 | ||
− | | | + | | [[#SegmentHeaderRelative|SegmentHeaderRelative]] for .api_info |
|- | |- | ||
| 0x90 | | 0x90 | ||
| 0x8 | | 0x8 | ||
− | | | + | | [[#SegmentHeaderRelative|SegmentHeaderRelative]] for .dynstr |
|- | |- | ||
| 0x98 | | 0x98 | ||
| 0x8 | | 0x8 | ||
− | | | + | | [[#SegmentHeaderRelative|SegmentHeaderRelative]] for .dynsym |
|- | |- | ||
| 0xA0 | | 0xA0 | ||
− | | 0x20 | + | | 0x20 |
− | | SHA256 | + | | TextHash (SHA256 hash over the decompressed .text section using the above size) |
+ | |- | ||
+ | | 0xC0 | ||
+ | | 0x20 | ||
+ | | RoHash (SHA256 hash over the decompressed .rodata section using the above size) | ||
+ | |- | ||
+ | | 0xE0 | ||
+ | | 0x20 | ||
+ | | DataHash (SHA256 hash over the decompressed .data section using the above size) | ||
|- | |- | ||
| 0x100 | | 0x100 | ||
− | | | + | | Variable |
| Compressed sections | | Compressed sections | ||
|} | |} | ||
Line 94: | Line 101: | ||
Most data in Switch binaries are standard ELF structures, however some are custom. | Most data in Switch binaries are standard ELF structures, however some are custom. | ||
For example, the MOD header is essentially a replacement for a PT_DYNAMIC program header. | For example, the MOD header is essentially a replacement for a PT_DYNAMIC program header. | ||
+ | |||
+ | == Flags == | ||
+ | {| class="wikitable" border="1" | ||
+ | |- | ||
+ | ! Bits | ||
+ | ! Description | ||
+ | |- | ||
+ | | 0 | ||
+ | | TextCompress (.text section is compressed) | ||
+ | |- | ||
+ | | 1 | ||
+ | | RoCompress (.rodata section is compressed) | ||
+ | |- | ||
+ | | 2 | ||
+ | | DataCompress (.data section is compressed) | ||
+ | |- | ||
+ | | 3 | ||
+ | | TextHash (.text hash must be checked when loading) | ||
+ | |- | ||
+ | | 4 | ||
+ | | RoHash (.rodata hash must be checked when loading) | ||
+ | |- | ||
+ | | 5 | ||
+ | | DataHash (.data hash must be checked when loading) | ||
+ | |} | ||
== SegmentHeader == | == SegmentHeader == | ||
Line 112: | Line 144: | ||
| 0x8 | | 0x8 | ||
| 0x4 | | 0x4 | ||
− | | | + | | Size (decompressed) |
|} | |} | ||
− | == . | + | == ModuleId == |
+ | Value of "build id" from ELF's GNU .note section. Contains variable sized digest, up to 32bytes. | ||
+ | |||
+ | == SegmentHeaderRelative == | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
|- | |- | ||
Line 124: | Line 159: | ||
| 0x0 | | 0x0 | ||
| 4 | | 4 | ||
− | | | + | | Offset |
|- | |- | ||
| 0x4 | | 0x4 | ||
| 4 | | 4 | ||
− | | | + | | Size |
|} | |} | ||
+ | |||
+ | Offset is relative to the .rodata section. | ||
== MOD == | == MOD == | ||
− | |||
− | |||
− | |||
− | |||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
|- | |- | ||
Line 144: | Line 177: | ||
| 0x00 | | 0x00 | ||
| 4 | | 4 | ||
− | | | + | | Reserved |
|- | |- | ||
| 0x04 | | 0x04 | ||
| 4 | | 4 | ||
− | | MagicOffset | + | | MagicOffset (always 8, so it works when MOD is at image_base + 0) |
|- | |- | ||
| 0x08 | | 0x08 | ||
Line 176: | Line 209: | ||
| 0x20 | | 0x20 | ||
| 4 | | 4 | ||
− | | | + | | Offset to runtime-generated module object (typically equal to .bss base) |
|} | |} | ||
− | =Arguments= | + | All offsets are signed 32bit values relative to the magic field. |
− | Loader maps memory and writes the [[Loader_services# | + | The 32bits at image base + 4 must point to the magic field. |
+ | The MOD structure is designed such that it can be placed at image base and point to itself. | ||
+ | The 2 fields preceding the magic field get copied around with the structure, even if it is relocated to somewhere besides the image base. If MOD is not located at image base, the value at offset 4 must still point to the MOD magic. In the case of .text being at image base, this implies that the first instruction can only be an unconditional branch over the offset literal. | ||
+ | |||
+ | = Arguments = | ||
+ | [[Loader_services|Loader]] maps memory and writes the [[Loader_services#SetProgramArgument|arguments]] to {end of rwdata section specified by last SegmentHeader}. Official processes use argdata_addr = {page-aligned _end}. svcQueryMemory is used by official sw to verify that argdata_addr is mapped RW, since this memory is only mapped when arguments are specified via that command. Afterwards, official sw aligns the argdata_addr to 4-bytes. | ||
+ | The structure located at argdata_addr is as follows: | ||
{| class="wikitable" border="1" | {| class="wikitable" border="1" | ||
|- | |- |
Revision as of 18:04, 22 February 2020
NSO is the main executable format.
It starts with the "NSO" header and mainly describes .text, .rodata, and .data segments (like a short-form of ELF program headers). If the segments are compressed, they are compressed using LZ4.
Header
Offset | Size | Description |
---|---|---|
0x0 | 4 | Magic "NSO0" |
0x4 | 4 | Version (always 0) |
0x8 | 4 | Reserved |
0xC | 4 | Flags |
0x10 | 0xC | SegmentHeader for .text |
0x1C | 0x4 | ModuleNameOffset (calculated by sizeof(header)) |
0x20 | 0xC | SegmentHeader for .rodata |
0x2C | 0x4 | ModuleNameSize |
0x30 | 0xC | SegmentHeader for .data |
0x3C | 0x4 | BssSize |
0x40 | 0x20 | ModuleId |
0x60 | 0x4 | TextFileSize (.text compressed size) |
0x64 | 0x4 | RoFileSize (.rodata compressed size) |
0x68 | 0x4 | DataFileSize (.data compressed size) |
0x6C | 0x1C | Reserved |
0x88 | 0x8 | SegmentHeaderRelative for .api_info |
0x90 | 0x8 | SegmentHeaderRelative for .dynstr |
0x98 | 0x8 | SegmentHeaderRelative for .dynsym |
0xA0 | 0x20 | TextHash (SHA256 hash over the decompressed .text section using the above size) |
0xC0 | 0x20 | RoHash (SHA256 hash over the decompressed .rodata section using the above size) |
0xE0 | 0x20 | DataHash (SHA256 hash over the decompressed .data section using the above size) |
0x100 | Variable | Compressed sections |
Most data in Switch binaries are standard ELF structures, however some are custom. For example, the MOD header is essentially a replacement for a PT_DYNAMIC program header.
Flags
Bits | Description |
---|---|
0 | TextCompress (.text section is compressed) |
1 | RoCompress (.rodata section is compressed) |
2 | DataCompress (.data section is compressed) |
3 | TextHash (.text hash must be checked when loading) |
4 | RoHash (.rodata hash must be checked when loading) |
5 | DataHash (.data hash must be checked when loading) |
SegmentHeader
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | FileOffset |
0x4 | 0x4 | MemoryOffset |
0x8 | 0x4 | Size (decompressed) |
ModuleId
Value of "build id" from ELF's GNU .note section. Contains variable sized digest, up to 32bytes.
SegmentHeaderRelative
Offset | Size | Description |
---|---|---|
0x0 | 4 | Offset |
0x4 | 4 | Size |
Offset is relative to the .rodata section.
MOD
Offset | Size | Description |
---|---|---|
0x00 | 4 | Reserved |
0x04 | 4 | MagicOffset (always 8, so it works when MOD is at image_base + 0) |
0x08 | 4 | Magic "MOD0" |
0x0C | 4 | .dynamic offset |
0x10 | 4 | .bss start offset |
0x14 | 4 | .bss end offset |
0x18 | 4 | .eh_frame_hdr start offset |
0x1C | 4 | .eh_frame_hdr end offset |
0x20 | 4 | Offset to runtime-generated module object (typically equal to .bss base) |
All offsets are signed 32bit values relative to the magic field. The 32bits at image base + 4 must point to the magic field. The MOD structure is designed such that it can be placed at image base and point to itself. The 2 fields preceding the magic field get copied around with the structure, even if it is relocated to somewhere besides the image base. If MOD is not located at image base, the value at offset 4 must still point to the MOD magic. In the case of .text being at image base, this implies that the first instruction can only be an unconditional branch over the offset literal.
Arguments
Loader maps memory and writes the arguments to {end of rwdata section specified by last SegmentHeader}. Official processes use argdata_addr = {page-aligned _end}. svcQueryMemory is used by official sw to verify that argdata_addr is mapped RW, since this memory is only mapped when arguments are specified via that command. Afterwards, official sw aligns the argdata_addr to 4-bytes.
The structure located at argdata_addr is as follows:
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | This is the total allocated space relative to argdata_addr, used for calculating the max size of the argv ptr array. Normally 0x9000? |
0x4 | 0x4 | This is the total_bytesize of the actual argdata string. |
0x8 | 0x18 | Unused by official sw. |
0x20 | See above | Actual argdata string. |
- The copy of the args used with the argv array is written by official processes to actual_argdata_string+actual_argdata_size.
- argv_ptrarray written by official processes is at (args_copy+actual_argdata_size) + 0x9 & ~0x7.