Difference between revisions of "NSO"

From Nintendo Switch Brew
Jump to navigation Jump to search
(surprised the page didn't already mention that compression is LZ4)
(27 intermediate revisions by 8 users not shown)
Line 1: Line 1:
 +
[[Category:File formats]]
 
NSO is the main executable format.
 
NSO is the main executable format.
  
It starts with a header:
+
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.
 +
 
 +
= NSO Header =
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
 
|-
 
|-
Line 14: Line 17:
 
| 0x4
 
| 0x4
 
| 4
 
| 4
|  
+
| NSO Version (Always 0)
 
|-
 
|-
 
| 0x8
 
| 0x8
 
| 4
 
| 4
|  
+
| Reserved (Unused)
 
|-
 
|-
 
| 0xC
 
| 0xC
 
| 4
 
| 4
|  
+
| Flags, bit 0-2: (.text, .rodata and .data) section is compressed, bit 3-5: check section hash when loading
 
|-
 
|-
 
| 0x10
 
| 0x10
 +
| 0xC
 +
| .text SegmentHeader
 +
|-
 +
| 0x1C
 +
| 0x4
 +
| Module offset (calculated by sizeof(header))
 +
|-
 +
| 0x20
 +
| 0xC
 +
| .rodata SegmentHeader
 +
|-
 +
| 0x2C
 +
| 0x4
 +
| Module file size
 +
|-
 +
| 0x30
 +
| 0xC
 +
| .data SegmentHeader
 +
|-
 +
| 0x3C
 +
| 0x4
 +
| bssSize
 +
|-
 +
| 0x40
 +
| 0x20
 +
| Value of "build id" from ELF's GNU .note section. Contains variable sized digest, up to 32bytes.
 +
|-
 +
| 0x60
 +
| 0x4
 +
| .text compressed size
 +
|-
 +
| 0x64
 +
| 0x4
 +
| .rodata compressed size
 +
|-
 +
| 0x68
 +
| 0x4
 +
| .data compressed size
 +
|-
 +
| 0x6C
 +
| 0x1C
 +
| Reserved (Padding)
 +
|-
 +
| 0x88
 +
| 0x8
 +
| .rodata-relative extents of .api_info
 +
|-
 +
| 0x90
 +
| 0x8
 +
| .rodata-relative extents of .dynstr
 +
|-
 +
| 0x98
 +
| 0x8
 +
| .rodata-relative extents of .dynsym
 +
|-
 +
| 0xA0
 +
| 0x20 * 3
 +
| SHA256 hashes over the decompressed sections using the above byte-sizes: .text, .rodata, and .data.
 +
|-
 +
| 0x100
 +
|
 +
| 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.
 +
 +
== SegmentHeader ==
 +
{| class="wikitable" border="1"
 +
|-
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 +
| 0x4
 +
| FileOffset
 +
|-
 +
| 0x4
 +
| 0x4
 +
| MemoryOffset
 +
|-
 +
| 0x8
 +
| 0x4
 +
| DecompressedSize
 +
|}
 +
 +
== .rodata-relative extent ==
 +
{| class="wikitable" border="1"
 +
|-
 +
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x0
 
| 4
 
| 4
|  
+
| RegionRoDataOffset
 
|-
 
|-
| 0x14
+
| 0x4
 
| 4
 
| 4
| .text offset? (0)
+
| RegionSize
 +
|}
 +
 
 +
== MOD ==
 +
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.
 +
{| class="wikitable" border="1"
 
|-
 
|-
| 0x18
+
! Offset
 +
! Size
 +
! Description
 +
|-
 +
| 0x00
 
| 4
 
| 4
| .text size
+
| ZeroPadding
 
|-
 
|-
| 0x1C
+
| 0x04
 
| 4
 
| 4
| .text required align? (0x1000)
+
| MagicOffset. Always 8 (so it works when MOD is at image_base + 0).
 
|-
 
|-
| 0x20
+
| 0x08
 
| 4
 
| 4
| .text compressed size?
+
| Magic "MOD0"
 
|-
 
|-
| 0x24
+
| 0x0C
 
| 4
 
| 4
| .rodata offset
+
| .dynamic offset
 
|-
 
|-
| 0x28
+
| 0x10
 
| 4
 
| 4
| .rodata size
+
| .bss start offset
 
|-
 
|-
| 0x2C
+
| 0x14
 
| 4
 
| 4
| .rodata required align? (1)
+
| .bss end offset
 
|-
 
|-
| 0x30
+
| 0x18
 
| 4
 
| 4
| .rodata compressed size?
+
| .eh_frame_hdr start offset
 
|-
 
|-
| 0x34
+
| 0x1C
 
| 4
 
| 4
| .data offset
+
| .eh_frame_hdr end offset
 
|-
 
|-
| 0x38
+
| 0x20
 
| 4
 
| 4
|
+
| offset to runtime-generated module object. typically equal to .bss base.
 +
|}
 +
 
 +
=Arguments=
 +
Loader maps memory and writes the [[Loader_services#AddProcessToLaunchQueue|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. Structure located at argdata_addr:
 +
 
 +
{| class="wikitable" border="1"
 +
|-
 +
! 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?
 
|-
 
|-
| 0x3C
+
| 0x4 || 0x4 || This is the total_bytesize of the actual argdata string.
| 4
 
| .bss size
 
 
|-
 
|-
| 0xA0
+
| 0x8 || 0x18 || Unused by official sw.
| 0x60
 
| 3 Hashes over the decompressed sections using the above byte-sizes: .text, .rodata, and .data.
 
 
|-
 
|-
| 0x100
+
| 0x20 || See above || Actual argdata string.
|
 
| Compressed sections
 
 
|}
 
|}
 +
 +
* 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.

Revision as of 19:20, 11 November 2018

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.

NSO Header

Offset Size Description
0x0 4 Magic "NSO0"
0x4 4 NSO Version (Always 0)
0x8 4 Reserved (Unused)
0xC 4 Flags, bit 0-2: (.text, .rodata and .data) section is compressed, bit 3-5: check section hash when loading
0x10 0xC .text SegmentHeader
0x1C 0x4 Module offset (calculated by sizeof(header))
0x20 0xC .rodata SegmentHeader
0x2C 0x4 Module file size
0x30 0xC .data SegmentHeader
0x3C 0x4 bssSize
0x40 0x20 Value of "build id" from ELF's GNU .note section. Contains variable sized digest, up to 32bytes.
0x60 0x4 .text compressed size
0x64 0x4 .rodata compressed size
0x68 0x4 .data compressed size
0x6C 0x1C Reserved (Padding)
0x88 0x8 .rodata-relative extents of .api_info
0x90 0x8 .rodata-relative extents of .dynstr
0x98 0x8 .rodata-relative extents of .dynsym
0xA0 0x20 * 3 SHA256 hashes over the decompressed sections using the above byte-sizes: .text, .rodata, and .data.
0x100 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.

SegmentHeader

Offset Size Description
0x0 0x4 FileOffset
0x4 0x4 MemoryOffset
0x8 0x4 DecompressedSize

.rodata-relative extent

Offset Size Description
0x0 4 RegionRoDataOffset
0x4 4 RegionSize

MOD

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.

Offset Size Description
0x00 4 ZeroPadding
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.

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. Structure located at argdata_addr:

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.