MachO 文件格式 - LC_SEGMENT_64 加载命令中“fileoff”字段的值

MachO file format - Value of `fileoff` field in LC_SEGMENT_64 load command

我编译了一个简单的程序如

int main()
{
    return 0; 
}

将 Clang 用于可执行文件并要求 otool 报告编译器生成的加载命令。我感兴趣的是 LC_SEGMENT_64,尤其是描述文件中 __TEXT 段的那个。我得到的描述是这样的:

$ otool -lV foo
foo:
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
   vmaddr 0x0000000000000000
   vmsize 0x0000000100000000
  fileoff 0
 filesize 0
  maxprot ---
 initprot ---
   nsects 0
    flags (none)
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 312
  segname __TEXT
   vmaddr 0x0000000100000000
   vmsize 0x0000000000001000
  fileoff 0
 filesize 4096
  maxprot rwx
 initprot r-x
   nsects 3
    flags (none)
Section
  sectname __text
   segname __TEXT
      addr 0x0000000100000f90
      size 0x000000000000000f
    offset 3984
     align 2^4 (16)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
 reserved1 0
 reserved2 0

我的问题是:为什么第二个加载命令中的 fileoff 字段设置为零?

Apple 对此字段的文档指出

The file is mapped starting at fileoff to the beginning of the segment in memory, vmaddr.

最初,这让我相信这个字段与 filesize 相结合,表明加载器是这样的:"Take the contents of the file from fileoff to fileoff + filesize and this is the sequence of instructions you're gonna ask the processor to run"。但如果这个值为零,我的假设当然不成立。

我认为,由于段至少有一个部分,加载程序将使用部分描述中的相应偏移值将代码定位到 运行,因此该值不是确实需要 --- 我们可以看到,事实上,该段中的第一部分具有 offset 字段的值(在本例中为 3984,我使用 otool -s __TEXT __text -j foo 对其进行了验证,并且确实指的是此部分在文件中的偏移量)。

但是,如果我对从同一源文件生成的目标文件执行相同的操作(即类型为 MH_OBJECT 而不是 MH_EXECUTE 的文件),我得到的结果是:

$ otool -lV foo.o
foo.o:
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 312
  segname
   vmaddr 0x0000000000000000
   vmsize 0x0000000000000070
  fileoff 464
 filesize 112
  maxprot rwx
 initprot rwx
   nsects 3
    flags (none)
Section
  sectname __text
   segname __TEXT
      addr 0x0000000000000000
      size 0x000000000000000f
    offset 464
     align 2^4 (16)
    reloff 0
    nreloc 0
      type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
 reserved1 0
 reserved2 0

在这种情况下,加载命令的 fileoff 字段确实有一个值,这与其第一部分 __text.

的值相同

otool 很难实现,但答案很简单 - 观察这里:

$ jtool -v -l /tmp/a | grep SEG
LC 00: LC_SEGMENT_64          Mem: 0x000000000-0x100000000  File: Not Mapped    ---/--__PAGEZERO
LC 01: LC_SEGMENT_64          Mem: 0x100000000-0x100001000  File: 0x0-0x1000    r-x/rw__TEXT
LC 02: LC_SEGMENT_64          Mem: 0x100001000-0x100002000  File: 0x1000-0x1098 r--/rw__LINKEDIT

__TEXT 段从文件的开头映射(或切片,如果是 fat ("universal"))。即用Mach-Oheader。这实际上是一个特性,因为 Mach-O 随后会被 dyld(您友好的加载器)解析为其他加载命令(尤其是库)。另一个问题是 __TEXT.__text 通常在同一个页面中,因此您无论如何都必须映射整个页面。