参考文档:
https://pdfs.semanticscholar.org/5096/25785304410039297b741ad2007e7ce0636b.pdf
CUDA Pro Tip: Understand Fat Binaries and JIT Caching | NVIDIA Technical Blog
cuda二进制文件中到底有什么 - 知乎
NVIDIA CUDA Compiler Driver
NVIDIA CUDA Compiler Driver
https://docs.nvidia.com/cuda/pdf/CUDA_Compiler_Driver_NVCC.pdf
cuda二进制文件中到底有些什么_真是使用gpu的二进制文件-CSDN博客
分析过程
首先编译一个CUDA程序,编译结果为标准的ELF文件,所以可以用binutils工具分析,查看可执行文件的section有那些。
$ readelf -S a.out
There are 35 section headers, starting at offset 0xa0fd8:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000270 00000270
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000000028c 0000028c
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 00000000000002ac 000002ac
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 00000000000002d0 000002d0
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000002f0 000002f0
0000000000000eb8 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000000011a8 000011a8
0000000000000796 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000000193e 0000193e
000000000000013a 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000001a78 00001a78
0000000000000150 0000000000000000 A 6 8 8
[ 9] .rela.dyn RELA 0000000000001bc8 00001bc8
00000000000037e0 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000000053a8 000053a8
0000000000000e10 0000000000000018 AI 5 27 8
[11] .init PROGBITS 00000000000061b8 000061b8
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000000061d0 000061d0
0000000000000970 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000006b40 00006b40
0000000000000008 0000000000000008 AX 0 0 8
[14] .text PROGBITS 0000000000006b50 00006b50
00000000000547fe 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 000000000005b350 0005b350
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 000000000005b360 0005b360
000000000000916c 0000000000000000 A 0 0 32
[17] .nv_fatbin PROGBITS 00000000000644d0 000644d0
0000000000000d28 0000000000000000 A 0 0 8
[18] __nv_module_id PROGBITS 00000000000651f8 000651f8
000000000000000f 0000000000000000 A 0 0 8
[19] .eh_frame_hdr PROGBITS 0000000000065208 00065208
0000000000002a8c 0000000000000000 A 0 0 4
[20] .eh_frame PROGBITS 0000000000067c98 00067c98
00000000000093e8 0000000000000000 A 0 0 8
[21] .gcc_except_table PROGBITS 0000000000071080 00071080
0000000000000010 0000000000000000 A 0 0 1
[22] .tbss NOBITS 0000000000271da0 00071da0
0000000000000298 0000000000000000 WAT 0 0 8
[23] .init_array INIT_ARRAY 0000000000271da0 00071da0
0000000000000098 0000000000000008 WA 0 0 8
[24] .fini_array FINI_ARRAY 0000000000271e38 00071e38
0000000000000008 0000000000000008 WA 0 0 8
[25] .data.rel.ro PROGBITS 0000000000271e40 00071e40
0000000000002a70 0000000000000000 WA 0 0 32
[26] .dynamic DYNAMIC 00000000002748b0 000748b0
0000000000000260 0000000000000010 WA 6 0 8
[27] .got PROGBITS 0000000000274b10 00074b10
00000000000004f0 0000000000000008 WA 0 0 8
[28] .data PROGBITS 0000000000275000 00075000
0000000000000058 0000000000000000 WA 0 0 32
[29] .nvFatBinSegment PROGBITS 0000000000275058 00075058
0000000000000030 0000000000000000 WA 0 0 8
[30] .bss NOBITS 00000000002750a0 00075088
00000000000014c0 0000000000000000 WA 0 0 32
[31] .comment PROGBITS 0000000000000000 00075088
00000000000003ef 0000000000000000 0 0 1
[32] .symtab SYMTAB 0000000000000000 00075478
000000000000f0c0 0000000000000018 33 1561 8
[33] .strtab STRTAB 0000000000000000 00084538
000000000001c952 0000000000000000 0 0 1
[34] .shstrtab STRTAB 0000000000000000 000a0e8a
000000000000014e 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
出现了三个普通ELF文件中没有的段:
它们分别是.nv_fatbin,__nv_module_id和.nvFatBinSegment三个段.进一步分析,.nv_fatbin和__nv_module_id属性是READONLY,而.nvFatBinSegment则是DATA,说明运行时.nv_fatbin和__nv_module_id为只读,而.nvFatBinSegment支持读写,说明CUDA的代码保存在nv_fatbin中,而nvFatBinSegment则放在数据段。
查看program header确实如此,.nv_fatbin __nv_module_id被放在只读R属性的segment,而.nvFatBinSegment和BSS,.data之类放在一起,是可读写的。
CUDA运行时的秘密就隐藏在这三个段中,分别解析,先从简单的入手,__nv_module_id只有15个字节,其内容是一组字符串,内容如其名,是描述信息,执行时的作用可能不大。
nvFatBinSegment 段文档中这样描述:
根据描述,nvFatBinSegment中保存的是CUDA程序的元数据,元数据是数据的数据,究竟是什么数据的数据,看来只能是描述CUDA程序的数据了,而前面分析CUDA程序在fatbin中,所以nvFatBinSegment段应该就是对fatbin的描述。
根据文档的描述,nvFatBinSegment 每6个word(24字节)为一组,其中每组第三个word为代码段在fatbin中的地址,并且nvFatBinSegment中的描述和fatbin要始终对应一致。
按照文档解析nvFatBinSegment,一共有两组,分别描述了fatbin中的两个代码区,地址能够对应的上。
仔细观察发现,每个GROUP内似乎都有一个ELF文件的,将其抠出来:
如下命令进行ELF hack,可以看到扣出的两个文件可以被解析,说明其确实是ELF文件,并且是CUDA ARCH:
objcopy -O binary --only-section .nv_fatbin a.out fat.bin
dd if=./fat.bin bs=1 skip=80 count=696 of=fat1.elf
dd if=./fat.bin bs=1 skip=776 of=fat2.elf
查看符号信息,fat1.elf没有有效内容,而FAT2.ELF中则存在写的KERNEL ADD函数的代码段:
反编译elf,fat1.elf没有指令,fat2.elf中包含了ADD的机器指令,从反编译的二进制可以看到这里的CUDA ISA是8个字节,也就是64BIT指令。
分析KERNEL流程:
$ nvdisasm -cfg fat2.elf|dot -ocfg.png -Tpng
文档中对FATBIN的描述,可见其确实包含多组ELF文件,至于为何多组ELF,多组的目的是什么,目前还是不甚了解。