跳转至

2023

GCC Compiler Option 1 : Optimization Options

手册

全体选项其中一部分是Optimize-Options

# 会列出可选项
g++ -march=native -m32 ... -Q --help=target 
# 会列出O3默认开启和关闭选项
g++ -O3 -Q --help=optimizers

编译时最好按照其分类有效组织, 例子如下:

g++ 
# Warning Options
-Wall -Werror -Wno-unknown-pragmas -Wno-dangling-pointer 
# Program Instrumentation Options
-fno-stack-protector
# Code-Gen-Options
-fno-exceptions -funwind-tables -fasynchronous-unwind-tables
# C++ Dialect
-fabi-version=2 -faligned-new -fno-rtti
# define
-DPIN_CRT=1 -DTARGET_IA32E -DHOST_IA32E -fPIC -DTARGET_LINUX 
# include
-I../../../source/include/pin 
-I../../../source/include/pin/gen 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/cxx/include 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/arch-x86_64 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi/asm-x86 
-I../../../extras/components/include 
-I../../../extras/xed-intel64/include/xed 
-I../../../source/tools/Utils 
-I../../../source/tools/InstLib 
# Optimization Options
-O3 -fomit-frame-pointer -fno-strict-aliasing 
-c -o obj-intel64/inscount0.o inscount0.cpp

常见选项

  • -Wxxx 对 xxx 启动warning,
  • -fxxx 启动xxx的编译器功能。-fno-xxx 关闭对应选项???
  • -gxxx debug 相关
  • -mxxx 特定机器架构的选项
名称 含义
-Wall 打开常见的所有warning选项
-Werror 把warning当成error
-std= C or C++ language standard. eg 'c++11' == 'c++0x' 'c++17' == 'c++1z', which 'c++0x','c++17' is develop codename
-Wunknown-pragmas 未知的pragma会报错(-Wno-unknown-pragmas 应该是相反的)
-fomit-frame-pointer 不生成栈帧指针,属于-O1优化
-Wstack-protector 没有防止堆栈崩溃的函数时warning (-fno-stack-protector)
-MMD only user header files, not system header files.
-fexceptions Enable exception handling.
-funwind-tables Unwind tables contain debug frame information which is also necessary for the handling of such exceptions
-fasynchronous-unwind-tables Generate unwind table in DWARF format. so it can be used for stack unwinding from asynchronous events
-fabi-version=n Use version n of the C++ ABI. The default is version 0.(Version 2 is the version of the C++ ABI that first appeared in G++ 3.4, and was the default through G++ 4.9.) ABI: an application binary interface (ABI) is an interface between two binary program modules. Often, one of these modules is a library or operating system facility, and the other is a program that is being run by a user.
-fno-rtti Disable generation of information about every class with virtual functions for use by the C++ run-time type identification features (dynamic_cast and typeid). If you don’t use those parts of the language, you can save some space by using this flag
-faligned-new Enable support for C++17 new of types that require more alignment than void* ::operator new(std::size_t) provides. A numeric argument such as -faligned-new=32 can be used to specify how much alignment (in bytes) is provided by that function, but few users will need to override the default of alignof(std::max_align_t). This flag is enabled by default for -std=c++17.
-Wl, xxx pass xxx option to linker, e.g., -Wl,-R/staff/shaojiemike/github/MultiPIM_icarus0/common/libconfig/lib specify a runtime library search path for dynamic libraries (shared libraries) during the linking process.

General Optimization Options

-O, -O2, -O3

-O3 turns on all optimizations specified by -O2

and also turns on the -finline-functions, -funswitch-loops, -fpredictive-commoning, -fgcse-after-reload, -ftree-loop-vectorize, -ftree-loop-distribute-patterns, -ftree-slp-vectorize, -fvect-cost-model, -ftree-partial-pre and -fipa-cp-clone options

-ffastmath

允许使用浮点计算获得更高的性能,但可能会略微降低精度。

-Ofast

更快但是有保证正确

-flto

(仅限 GNU)链接时优化,当程序链接时检查文件之间的函数调用的步骤。该标志必须用于编译和链接时。使用此标志的编译时间很长,但是根据应用程序,当与 -O* 标志结合使用时,可能会有明显的性能改进。这个标志和任何优化标志都必须传递给链接器,并且应该调用 gcc/g++/gfortran 进行链接而不是直接调用 ld。

-mtune=processor

此标志对特定处理器类型进行额外调整,但它不会生成额外的 SIMD 指令,因此不存在体系结构兼容性问题。调整将涉及对处理器缓存大小、首选指令顺序等的优化。

在 AMD Bulldozer 节点上使用的值为 bdver1,在 AMD Epyc 节点上使用的值为 znver2。是zen ver2的简称。

Optimization Options: 数据预取相关

  1. -fprefetch-loop-arrays
  2. 如果目标机器支持,生成预取内存的指令,以提高访问大数组的循环的性能。这个选项可能产生更好或更差的代码;结果在很大程度上取决于源代码中的循环结构。
  3. -Os禁用

Optimization Options: 访存优化相关

https://zhuanlan.zhihu.com/p/496435946

下面没有特别指明都是O3,默认开启

调整数据的访问顺序

  1. -ftree-loop-distribution
  2. 允许将一个复杂的大循环,拆开成多个循环,各自可以继续并行和向量化
  3. -ftree-loop-distribute-patterns
  4. 类似上面一种?
  5. -floop-interchange
  6. 允许交换多层循环次序来连续访存
  7. -floop-unroll-and-jam
  8. 允许多层循环,将外循环按某种系数展开,并将产生的多个内循环融合。

代码段对齐

(不是计算访问的数据)

  1. -falign-functions=n:m:n2:m2
  2. Enabled at levels -O2, -O3. 类似有一堆

调整代码块的布局

  1. -freorder-blocks
  2. 函数基本块重排来,减少分支

Optimization Options: Unroll Flags

-funroll-loops

Unroll loops whose number of iterations can be determined at compile time or upon entry to the loop. -funroll-loops implies -frerun-cse-after-loop. This option makes code larger, and may or may not make it run faster.

-funroll-all-loops

Unroll all loops, even if their number of iterations is uncertain when the loop is entered. This usually makes programs run more slowly. -funroll-all-loops implies the same options as -funroll-loops,

max-unrolled-insns

The maximum number of instructions that a loop should have if that loop is unrolled, and if the loop is unrolled, it determines how many times the loop code is unrolled. 如果循环被展开,则循环应具有的最大指令数,如果循环被展开,则它确定循环代码被展开的次数。

max-average-unrolled-insns

The maximum number of instructions biased by probabilities of their execution that a loop should have if that loop is unrolled, and if the loop is unrolled, it determines how many times the loop code is unrolled. 如果一个循环被展开,则根据其执行概率偏置的最大指令数,如果该循环被展开,则确定循环代码被展开的次数。

max-unroll-times

The maximum number of unrollings of a single loop. 单个循环的最大展开次数。

Optimization Options: SIMD Instructions

-march=native

会自动检测,但有可能检测不对。

-march="arch"

这将为特定架构生成 SIMD 指令并应用 -mtune 优化。 arch 的有用值与上面的 -mtune 标志相同。

g++ -march=native -m32 ... -Q --help=target

-mtune=                               skylake-avx512 

 Known valid arguments for -march= option:
    i386 i486 i586 pentium lakemont pentium-mmx winchip-c6 winchip2 c3 samuel-2 c3-2 nehemiah c7 esther i686 pentiumpro pentium2 pentium3 pentium3m pentium-m pentium4 pentium4m prescott nocona core2 nehalem corei7 westmere sandybridge corei7-avx ivybridge core-avx-i haswell core-avx2 broadwell skylake skylake-avx512 cannonlake icelake-client icelake-server cascadelake tigerlake bonnell atom silvermont slm goldmont goldmont-plus tremont knl knm intel geode k6 k6-2 k6-3 athlon athlon-tbird athlon-4 athlon-xp athlon-mp x86-64 eden-x2 nano nano-1000 nano-2000 nano-3000 nano-x2 eden-x4 nano-x4 k8 k8-sse3 opteron opteron-sse3 athlon64 athlon64-sse3 athlon-fx amdfam10 barcelona bdver1 bdver2 bdver3 bdver4 znver1 znver2 btver1 btver2 generic native

-msse4.2 -mavx -mavx2 -march=core-avx2

dynamic flags

-fPIC

position-independent code(PIC)

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://blog.csdn.net/daidodo/article/details/2185222

https://www.bu.edu/tech/support/research/software-and-programming/programming/compilers/gcc-compiler-flags/

C program compile&run process

编译总流程

编译是指编译器读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码。

  1. 预处理阶段:
  2. #include语句以及一些宏插入程序文本中,得到main.isum.i文件。
  3. 编译阶段:
  4. 将文本文件main.isum.i编译成文本文件main.ssum.c的汇编语言程序。 低级的汇编语言为不同的高级语言提供了通用输出语言。
  5. 汇编阶段:
  6. main.ssum.s翻译成机器语言的二进制指令,并打包成一种叫做可重定位目标程序的格式,并将结果保存在main.o和sum.o两个文件中。这种文件格式就比较接近elf格式了。
  7. 链接阶段:
  8. 合并main.osum.o,得到可执行目标文件,就是elf格式文件。

目标文件

目标文件有三种形式:

  • 可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
  • 可执行目标文件。包含二进制代码和数据,其形式可以被直接复制到内存并执行。
  • 共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接。

1 预处理

  • 预处理器: 将.c 文件转化成 .i文件.

生成预处理文件

  • 使用的gcc命令是:gcc –E filename.cpp -o filename.i
  • -E Preprocess only; do not compile, assemble or link.
  • 通过-C能保留头文件里的注释,如gcc -E -C circle.c -o circle.c
  • 另一种方式 gcc -save-temps -c -o main.o main.c
  • 也可以调用cpp filename.cpp -o filename.i命令

理解预处理文件

  • 输出文件会出现许多名叫 linemarkers类似# linenum filename flags的注释,这些注释是为了让编译器能够定位到源文件的行号,以便于编译器能够在编译错误时给出正确的行号。
  • They mean that the following line originated in file filename at line linenum.
  • flags meaning
    • ‘1’ This indicates the start of a new file.
    • ‘2’ This indicates returning to a file (after having included another file)
    • ‘3’ This indicates that the following text comes from a system header file, so certain warnings should be suppressed
    • ‘4’ This indicates that the following text should be treated as being wrapped in an implicit extern "C" block.
    • ‘4’表示接下来的文本应被视为被包含在隐式的“extern "C"”块中。在C++中,函数名和变量名可以有不同的命名空间,但是使用“extern "C"”修饰时可以取消这种区别,使得函数名和变量名可以在C++和C代码之间共享。因此,在C++中使用“extern "C"”来声明C函数或变量时,需要使用‘4’来指示编译器此处的文本应该被视为C代码,而不是C++代码。[来自chatGPT的解释]

预处理内容(过程)

除开注释被替换成空格,包括代码里的预处理命令:

  1. #error "text" 的作用是在编译时生成一个错误消息,它会导致编译过程中断。 同理有#warning
  2. 宏定义指令,如 #define a b 对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。
  3. 条件编译指令,如#ifdef SNIPER#if defined SNIPER && SNIPER == 0,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
  4. gcc编译使用-DSNIPER=5
  5. 头文件包含指令,如#include "FileName"或者#include 等。 该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
  6. 特殊符号,预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。 预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

头文件搜索优先级

#include "" vs #include <> 区别在于前者会在文件的当前目录寻找,但是后者只会在编译器编译的official路径寻找

通常的搜索顺序是:

  • 包含指定源文件的目录(对于在 #include 命令中以引号包括的文件名)。
  • 采用-iquote选项指定的目录,依照出现在命令行中的顺序进行搜索。只对 #include 命令中采用引号的头文件名进行搜索。
  • 所有header file的搜寻会从-I开始, 依照出现在命令行中的顺序进行搜索。(可以使用-I/path/file只添加一个头文件,尤其是在编译的兼容性修改时)
  • 采用环境变量 CPATH 指定的目录。
  • 采用-isystem选项指定的目录,依照出现在命令行中的顺序进行搜索。
  • 然后找环境变量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH指定的路径
  • 再找系统默认目录(/usr/include、/usr/local/include、/usr/lib/gcc-lib/i386-linux/2.95.2/include......)

  • 通过如下命令可以查看头文件搜索目录 gcc -xc -E -v - < /dev/null 或者 g++ -xc++ -E -v - < /dev/null*. 如果想改,需要重新编译gcc

  • 或者在编译出错时,g++ -H -v查看是不是项目下的同名头文件优先级高于sys-head-file

2 编译优化Compile

  • 将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程
  • 一般将.c/.h或者.i文件转换成.s文件,

生成汇编代码

  • 使用的gcc命令是:gcc –S filename.cpp -o filename.s,对应于
  • -S Compile only; do not assemble or link.
  • 理论上gcc –S filename.i -o filename.s 也是可行的。但是我遇到头文件冲突的问题error: declaration for parameter ‘__u_char’ but no such parameter
  • 编译命令 cc –S filename.cpp -o filename.s
  • 或者cc1命令

编译内容(过程)

  • 词法分析、语法分析、语意分析、中间代码生成,在语法检查、类型检查之后,将其翻译成等价的中间代码表示或汇编代码
  • 优化(-O3
  • 常规优化:删除死代码、减少寄存器传输、常量折叠、提取中间量
  • 高阶优化:循环展开、指针优化、函数内联,自动SIMD向量化
  • 关于内联函数
  • 内联函数是在函数定义前加上关键字inline的函数。它用于请求编译器将函数的代码插入到每个调用该函数的地方,而不是通过函数调用来执行。这样可以减少函数调用的开销,提高程序的执行效率。
  • 内联函数一般适用于函数体较小、频繁调用的函数,但最终是编译器决定是否将函数内联,编译器可以忽略对内联函数的请求。

如果想把 C 语言变量的名称作为汇编语言语句中的注释,可以加上 -fverbose-asm 选项:

gcc -S -O3 -fverbose-asm ../src/pivot.c -o pivot_O1.s
objdump -Sd ../build/bin/pivot > pivot1.s

理解汇编文件

请阅读 GNU assembly file一文

3 汇编assemble

汇编器:将.s 文件转化成 .o文件,

生成可重定位目标程序

  • 使用的gcc 命令是:gcc –c
  • -c Compile and assemble, but do not link.
  • 汇编命令是 as

汇编过程

  • 汇编实际上指汇编器(as)把汇编语言代码翻译成目标机器指令(二进制)的过程。
  • 目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
  • 目标文件由段组成。通常一个目标文件中至少有两个段:

  • 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

  • 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

查看理解

  • 查看汇编代码 objdump -Sd ../build/bin/pivot > pivot1.s
  • -S 以汇编代码的形式显示C++原程序代码,如果有debug信息,会显示源代码。
  • nm file.o 查看目标文件中的符号表

注意,这时候的目标文件里的使用的函数可能没定义,需要链接其他目标文件.a .so .o .dll(Dynamic Link Library的缩写,Windows动态链接库)

nm 命令

List symbol names in object files.

no symbols

  • 编译时可能会为了空间优化掉许多符号表,例如 -O3 Release模式
  • 切换成debug模式

常用选项 -CD

  • -C 选项告诉 nm 将 C++ 符号的 mangled 名称转换为原始的、易于理解的名称。
  • -g:仅显示外部符号。
  • -D / --dynamic:显示动态符号,这在查看共享库(如 .so 文件)时非常有用。
  • -l:显示本地(static)符号。
  • --defined-only:仅显示已定义的符号。

输出

  1. 符号值。默认显示十六进制,也可以指定;
  2. 符号类型。小写表示是本地符号,大写表示全局符号(external);
  3. 符号名称, 如下
符号类型 描述
A 符号值是绝对的。在进一步的连接中,不会被改变。
B 符号位于未初始化数据段(known as BSS).
C 共用(common)符号. 共用符号是未初始化的数据。在连接时,多个共用符号可能采用一个同样的名字,如果这个符号在某个地方被定义,共用符号被认为是未定义的引用.
D 已初始化数据段的符号
G 已初始化数据段中的小目标(small objective)符号. 一些目标文件格式允许更有效的访问小目标数据,比如一个全局的int变量相对于一个大的全局数组。
I 其他符号的直接应用,这是GNU扩展的,很少用了. N 调试符号.
R 只读数据段符号. S 未初始化数据段中的小目标(small object)符号.
T 代码段的符号.
U 未定义符号.
V 弱对象(weak object)符号. 当一个已定义的弱符号被连接到一个普通定义符号,普通定义符号可以正常使用,当一个未定义的弱对象被连接到一个未定义的符号,弱符号的值为0.
W 一个没有被指定一个弱对象符号的弱符号(weak symbol)。 - a.out目标文件中的刺符号(stabs symbol). 这种情况下,打印的下一个值是其他字段,描述字段,和类型。刺符号用于保留调试信息.
? 未知符号类型,或者目标文件特有的符号类型.

ldconfig 查找动态库位置

ldconfig 命令用于配置动态链接器的运行时绑定。你可以使用它来查询系统上已知的库文件的位置。

查询 libdw.so 的位置:

ldconfig -p | grep libdw

ldd 检查是否链接成功

ldd会显示动态库的链接关系,中间的nmU没关系,只需要最终.so对应符号是T即可。

4 链接过程

通过使用ld命令,将编译好的目标文件连接成一个可执行文件或动态库。

  • 链接器的核心工作就是符号表解析、重定位和库文件链接三个部分。(具体细节看CSAPP7.5-7.7)
  • 符号解析
    • 每个可重定位目标程序中都存在符号表的数据结构,包含了一些被声明的函数和变量的符号。依上例,main.o和sum.o都有一个这样的结构。符号表中的每一项都包含一个符号名字和一个符号定义的地址。
    • 符号解析的任务就是将这些符号和它们所在的源文件、库文件中的定义进行匹配。这个过程会生成符号表,用于给链接器在后续的重定位中找到函数所在的地址。
    • 对于符号解析有重载(不同的类,函数名相同)的特殊情况,比如Foo::bar(int,long)会变成bar__3Fooil。其中3是名字字符数
  • 重定位:在符号解析完成后,链接器会把不同的目标文件合并在一起,此时就需要对目标代码进行地址的修正,使得各个目标文件之间的函数调用或者变量访问都可以正确。这个过程叫做重定位。链接器会根据符号表信息,将每个函数调用位置中的符号替换成实际的地址。
  • 库文件链接:链接器还需要为程序链接不同的库文件,包括系统库和用户库。这些库文件可能是静态库或者动态库。
    • 如果是静态库,链接器会从库文件中提取目标代码并将其与目标文件合并成一个可执行文件。
    • 如果是动态库,则需要在运行时动态加载库文件,并将其链接到应用程序中。

符号和符号表

见 Linux Executable file: Structure & Running

符号解析

  • 局部变量
  • 编译器只允许每个模块中每个局部符号有一个定义。同时确保它们拥有唯一的名字。
  • 全局变量
  • 缺失情况:当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用符号的定义,就输出undefined reference to
  • 同名情况:编译器报错或者选择一个,
    • 函数和已初始化的全局变量是强符号,
    • 未初始化的全局变量是弱符号。
  • 选择规则:
    • 规则 1:不允许有多个同名的强符号。
    • 规则 2:如果有一个强符号和多个弱符号同名,那么选择强符号。
    • 规则 3:如果有多个弱符号同名,那么从这些弱符号中任意选择一个。
    • 规则 2 和规则 3 的应用会造成一些不易察觉的运行时错误,对于不警觉的程序员来说,是很难理解的,尤其是如果重复的符号定义还有不同的类型时。

重定位

一旦链接器完成了符号解析这一步,就把代码中的每个符号引用和正好一个符号定义(即它的一个输入目标模块中的一个符号表条目)关联起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小。现在就可以开始重定位步骤了,在这个步骤中,将合并输入模块,并为每个符号分配运行时地址。重定位由两步组成:

  • 重定位节和符号定义
  • 在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。
    • 例如,来自所有输入模块的.data 节被全部合并成一个节,这个节成为输出的可执行目标文件的.data 节。
    • 然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。
    • 当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
  • 重定位节中的符号引用
  • 在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。
  • 要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目(relocation entry)的数据结构,我们接下来将会描述这种数据结构。
重定位条目

当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。

代码的重定位条目放在 .rel.text 中。已初始化数据的重定位条目放在 .rel.data 中。

下面 展示了 ELF 重定位条目的格式。

  1. offset 是需要被修改的引用的节偏移。
  2. symbol 标识被修改引用应该指向的符号。
  3. type 告知链接器如何修改新的引用。
  4. ELF 定义了 32 种不同的重定位类型,有些相当隐秘。我们只关心其中两种最基本的重定位类型:
    1. R_X86_64_PC32。重定位一个使用 32 位 PC 相对地址的引用。回想一下 3.6.3 节,一个 PC 相对地址就是距程序计数器(PC)的当前运行时值的偏移量。当 CPU 执行一条使用 PC 相对寻址的指令时,它就将在指令中编码的 32 位值加上 PC 的当前运行时值,得到有效地址(如 call 指令的目标),PC 值通常是下一条指令在内存中的地址。(将 PC 压入栈中来使用)
    2. R_X86_64_32。重定位一个使用 32 位绝对地址的引用。通过绝对寻址,CPU 直接使用在指令中编码的 32 位值作为有效地址,不需要进一步修改。
  5. addend 是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。
typedef struct {
    long offset;    /* Offset of the reference to relocate */
    long type:32,   /* Relocation type */
         symbol:32; /* Symbol table index */
    long addend;    /* Constant part of relocation expression */
} Elf64_Rela;

目标文件与库的位置

链接器通常从左到右解析依赖项,这意味着如果库 A 依赖于库 B,那么库 B 应该在库 A 之前被链接。

库顺序

假设有三个库 libA, libB, 和 libC,其中 libA 依赖 libB,而 libB 又依赖 libC。在 CMake 中,你应该这样链接它们:

target_link_libraries(your_target libC libB libA)

这样的顺序确保了当链接器处理 libA 时,libB 和 libC 中的符号已经可用。

书上截图

4.1 静态链接

静态库static library就是将相关的目标模块打包形成的单独的文件。使用ar命令。

  • 在Linux系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中。
  • 存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。
  • 存档文件名由后缀.a标识。

优点与问题

静态库的优点在于:

  • 程序员不需要显式的指定所有需要链接的目标模块,因为指定是一个耗时且容易出错的过程;
  • 链接时,连接程序只从静态库中拷贝被程序引用的目标模块,这样就减小了可执行文件在磁盘和内存中的大小。

问题:

  • 几乎所有程序都需要printf这样的库函数,每个可执行文件都包含该模块的代码段和数据段,浪费磁盘空间。
  • linux采用虚拟内存管理内存分配,每个进程的内存空间是独立的,运行时所有程序都要把这些库函数代码段和数据段加载到自己的内存里,浪费内存。
  • 静态库和所有的软件一样,需要定期维护和更新。如果应用程序员想要使用一个库的最新版本,他们必须以某种方式了解到该库的更新情况,然后显式地将他们的程序与更新了的库重新链接。

静态链接过程

深入理解计算机系统P477,静态库例子

gcc -static -o prog2c main2.o -L. -lvector

图 7-8 概括了链接器的行为。-static 参数告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,它可以加载到内存并运行,在加载时无须更进一步的链接。-lvector 参数是 libvector.a 的缩写,-L. 参数告诉链接器在当前目录下查找 libvector.a。

  • 当链接器运行时,它判定 main2.o 引用了 addvec.o 定义的 addvec 符号,所以复制 addvec.o 到可执行文件。
  • 因为程序不引用任何由 multvec.o 定义的符号,所以链接器就不会复制这个模块到可执行文件。
  • 链接器还会复制 libc.a 中的 printf.o 模块,以及许多 C 运行时系统中的其他模块。

4.2 动态链接

  • 共享库(shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。
  • 共享库也称为共享目标(shared object),在 Linux 系统中通常用 .so 后缀来表示。微软的操作系统大量地使用了共享库,它们称为 DLL(动态链接库)。
  • 这个过程称为动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker)的程序来执行的。

共享库是以两种不同的方式来“共享”的:

  • 首先,在任何给定的文件系统中,对于一个库只有一个. so 文件。所有引用该库的可执行目标文件共享这个 .so 文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用它们的可执行的文件中。
  • 其次,在内存中,一个共享库的 .text 节的一个副本可以被不同的正在运行的进程共享

ld-link

如上创建了一个可执行目标文件 prog2l,而此文件的形式使得它在运行时可以和 libvector.so 链接。基本的思路是:

  • 当创建可执行文件时,静态执行一些链接
  • 此时,没有任何 libvector.so 的代码和数据节真的被复制到可执行文件 prog2l 中。反之,链接器复制了一些重定位和符号表信息,它们使得运行时可以解析对 libvector.so 中代码和数据的引用。
  • 然后在程序加载时,动态完成链接过程。
  • 动态链接可以在可执行文件第一次加载和运行时发生(加载时链接)
    • Common case for Linux,handled automatically by the dynamic linker (ld-linux.so).
    • Standard C library (libc.so)usually dynamically linked.
  • 动态链接也可以在程序开始运行后发生(运行时链接).
    • In Linux,this is done by calls to the dlopen() interface.
    • Distributing software.
    • High-performance web servers.
    • Runtime library interpositioning.

加载情况一

情况:在应用程序被加载后执行前时,动态链接器加载和链接共享库的情景。

核心思想:由动态链接器接管,加载管理和关闭共享库(比如,如果没有其他共享库还在使用这个共享库,dlclose函数就卸载该共享库。)。

  1. 首先,加载部分链接的可执行文件 prog2l。
  2. prog2l 包含一个 .interp 节,这一节包含动态链接器的路径名,动态链接器本身就是一个共享目标(如在 Linux 系统上的 ld-linux.so). 加载器不会像它通常所做地那样将控制传递给应用,而是加载和运行这个动态链接器。然后,动态链接器通过执行下面的重定位完成链接任务:
  3. 重定位 libc.so 的文本和数据到某个内存段。
  4. 重定位 libvector.so 的文本和数据到另一个内存段。
  5. 重定位 prog2l 中所有对由 libc.so 和 libvector.so 定义的符号的引用。

最后,动态链接器将控制传递给应用程序。从这个时刻开始,共享库的位置就固定了,并且在程序执行的过程中都不会改变。

加载情况二

情况:应用程序在运行时要求动态链接器加载和链接某个共享库,而无需在编译时将那些库链接到应用。

实际应用情况:

  • 分发软件。微软 Wmdows 应用的开发者常常利用共享库来分发软件更新。他们生成一个共库的新版本,然后用户可以下载,并用它替代当前的版本。下一次他们运行应用程序时,应用将自动链接和加载新的共享库。
  • 构建高性能 Web 服务器。
  • 许多 Web 服务器生成动态内容,比如个性化的 Web 页面、账户余额和广告标语
  • 早期的 Web 服务器通过使用 fork 和 execve 创建一个子进程,并在该子进程的上下文中运行 CGI 程序来生成动态内容。
  • 然而,现代高性能的 Web 服务器可以使用基于动态链接的更有效和完善的方法来生成动态内容。

思路是将每个生成动态内容的函数打包在共享库中。

  1. 当一个来自 Web 浏览器的请求到达时,服务器动态地加载和链接适当的函数,然后直接调用它,而不是使用 fork 和 execve 在子进程的上下文中运行函数。
  2. 函数会一直缓存在服务器的地址空间中,所以只要一个简单的函数调用的开销就可以处理随后的请求了。这对一个繁忙的网站来说是有很大影响的。更进一步地说,在运行时无需停止服务器,就可以更新已存在的函数,以及添加新的函数。

动态库的优点

  • 更新动态库,无需重新链接;对于大系统,重新链接是一个非常耗时的过程;
  • 运行中可供多个程序使用,内存中只需要有一份,节省内存。运行时一个共享库的代码段和数据段在物理内存中只有一份,但映射到多个虚拟内存片段上,供不同程序使用。其中代码段是只读的,整个操作系统绝对只有一份。但数据段有可能被修改,在修改的时候则会复制一个副本,每个进程有自己的一个内存副本。
  • 共享库是.so文件,不会和我们自己的代码一起合并成可执行文件,不占磁盘空间。

动态链接

fPIC,fPIE

编译器yasm的参数-DPIE

如果同一份代码可能被加载到进程空间的任意虚拟地址上执行(如共享库和动态加载代码),那么就需要使用-fPIC生成位置无关代码。

如何实现动态链接

  1. 共享库是.so文件,不会和我们自己的代码一起合并成可执行文件,不占磁盘空间。
  2. 运行时一个共享库的代码段和数据段在物理内存中只有一份,但映射到多个虚拟内存片段上,供不同程序使用。
  3. 其中代码段是只读的,整个操作系统绝对只有一份。
  4. 但数据段有可能被修改,在修改的时候则会复制一个副本,每个进程有自己的一个内存副本。
  5. 共享库的代码段和数据段加载到任意的内存段中,位置不固定。
  6. 加载完成后,进行符号重定位。回想一下之前说过的重定位过程,需要修改所有符号引用的地址。
  7. 由于动态链接在运行时才确定共享库代码段和数据段的内存地址,所以在运行时才能进行重定位。
  8. 运行时修改代码,想想就觉得不优雅。而且Linux不允许在运行时修改代码段。
  9. 由此,要完成动态链接,还需要引入了最后一个重要的概念,位置无关代码,即在加载时无需重定位的代码。

位置无关代码(Position-Independent Code, PIC)

  • 问题:多个进程是如何共享程序的一个副本的呢?
    • 一种方法是给每个共享库分配一个事先预备的专用的(虚拟)地址空间片,然后要求加载器总是在这个地址加载共享库。
  • 问题。
    • 地址空间的使用效率不高,因为即使一个进程不使用这个库,那部分空间还是会被分配出来。
    • 难以管理。我们必须保证没有片会重叠。
      • 库修改了之后,我们必须确认已分配给它的片还适合它的大小。如果不适合了,必须找一个新的片。
      • 创建了一个新的库,我们还必须为它寻找空间。随着时间的进展,假设在一个系统中有了成百个库和库的各个版本库,就很难避免地址空间分裂成大量小的、未使用而又不再能使用的小洞。
      • 更糟的是,对每个系统而言,库在内存中的分配都是不同的,这就引起了更多令人头痛的管理问题。
  • 可以加载而无需重定位的代码称为位置无关代码(Position-Independent Code,PIC)

    • 无限多个进程可以共享一个共享模块的代码段的单一副本。(当然,每个进程仍然会有它自己的读/写数据块。)
  • 在一个 x86-64 系统中,对同一个目标模块中符号的引用是不需要特殊处理使之成为 PIC。可以用 PC 相对寻址来编译这些引用,构造目标文件时由静态链接器重定位。

  • 然而,对共享模块定义的外部过程和对全局变量的引用需要一些特殊的技巧,接下来我们会谈到。

PIC 数据引用

  • 目标:生成对全局变量的 PIC 引用
  • 思想:无论我们在内存中的何处加载一个目标模块(包括共享目标模块),数据段与代码段的距离总是保持不变。因此,代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量,与代码段和数据段的绝对内存位置是无关的。由于数据段是可以在运行时修改的,所以可以把对代码段的修改转化为对数据段的修改。
  • 实现:在数据段前面加入一个数据结构,全局偏移量表(Global Offset Table,GOT)。每一个被该模块引用的全局数据目标(过程或全局变量),都在GOT里有一个8字节条目,并为每个条目生成一个重定位条目。
  • 实际使用:在加载时,动态链接器会重定位 GOT 中的每个条目,使得它包含目标的正确的绝对地址。然后程序执行时就能正确访问正确的绝对地址了。

PIC 函数调用

  • 情况:假设程序调用一个由共享库定义的函数。编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。
  • 简单方法:为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。不过,这种方法并不是 PIC,因为它需要链接器修改调用模块的代码段。

解决方法:延迟绑定(lazy binding),将过程地址的绑定推迟到第一次调用该过程时。

动机:使用延迟绑定的动机是对于一个像 libc.so 这样的共享库输出的成百上千个函数中,一个典型的应用程序只会使用其中很少的一部分。把函数地址的解析推迟到它实际被调用的地方,能避免动态链接器在加载时进行成百上千个其实并不需要的重定位。

结果:第一次调用过程的运行时开销很大,但是其后的每次调用都只会花费一条指令和一个间接的内存引用。

实现:延迟绑定是通过两个数据结构之间简洁但又有些复杂的交互来实现的,这两个数据结构是:GOT 和过程链接表(Procedure Linkage Table,PLT)。如果一个目标模块调用定义在共享库中的任何函数,那么它就有自己的 GOT 和 PLT。GOT 是数据段的一部分,而 PLT 是代码段的一部分。

首先,让我们介绍这两个表的内容。

  • 过程链接表(PLT)。PLT 是一个数组,其中每个条目是 16 字节代码。
  • PLT[0] 是一个特殊条目,它跳转到动态链接器中。
  • 每个被可执行程序调用的库函数都有它自己的 PLT 条目。每个条目都负责调用一个具体的函数。
  • PLT[1](图中未显示)调用系统启动函数(__libc_start_main),它初始化执行环境,调用 main 函数并处理其返回值从 PLT[2] 开始的条目调用用户代码调用的函数。在我们的例子中,PLT[2] 调用 addvec,PLT[3](图中未显示)调用 printf。
  • 全局偏移量表(GOT)。正如我们看到的,GOT 是一个数组,其中每个条目是 8 字节地址。
  • 和 PLT 联合使用时,GOT[O] 和 GOT[1] 包含动态链接器在解析函数地址时会使用的信息。GOT[2] 是动态链接器在 ld-linux.so 模块中的入口点。
  • 其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的 PLT 条目。例如,GOT[4] 和 PLT[2] 对应于 addvec。初始时,每个 GOT 条目都指向对应 PLT 条目的第二条指令。

PLT

上图a 展示了 GOT 和 PLT 如何协同工作,在 addvec 被第一次调用时,延迟解析它的运行时地址:

  1. 第 1 步。不直接调用 addvec,程序调用进入 PLT[2],这是 addvec 的 PLT 条目。
  2. 第 2 步。第一条 PLT 指令通过 GOT[4] 进行间接跳转。因为每个 GOT 条目初始时都指向它对应的 PLT 条目的第二条指令,这个间接跳转只是简单地把控制传送回 PLT[2] 中的下一条指令。
  3. 第 3 步。在把 addvec 的 ID(0x1)压入栈中之后,PLT[2] 跳转到 PLT[0]。
  4. 第 4 步。PLT[0] 通过 GOT[1] 间接地把动态链接器的一个参数压入栈中,然后通过 GOT[2] 间接跳转进动态链接器中。动态链接器使用两个栈条目来确定 addvec 的运行时位置,用这个地址重写 GOT[4],再把控制传递给 addvec。

上图b 给出的是后续再调用 addvec 时的控制流:

  1. 第 1 步。和前面一样,控制传递到 PLT[2]。
  2. 第 2 步。不过这次通过 GOT[4] 的间接跳转会将控制直接转移到 addvec。
库搜索优先级

静态库

  1. gcc先从-L寻找;
  2. 再找环境变量LIBRARY_PATH指定的搜索路径;
  3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的。

动态库

  1. 编译目标代码时指定的动态库搜索路径-L;
  2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
  3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
  4. 默认的动态库搜索路径/lib /usr/lib/ /usr/local/lib
shaojiemike@snode6 /lib/modules/5.4.0-107-generic/build  [06:32:26]
> gcc -print-search-dirs
install: /usr/lib/gcc/x86_64-linux-gnu/9/
programs: =/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/bin/
libraries: =/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/9/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/9/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/

5. 加载器

加载器:将可执行程序加载到内存并进行执行,loader和ld-linux.so。

将可执行文件加载运行

其他技巧:GNU用于理解和处理目标文件的相关命令

命令 描述
ar 创建静态库,插入、删除、列出和提取成员;
stringd 列出目标文件中所有可以打印的字符串;
strip 从目标文件中删除符号表信息;
nm 列出目标文件符号表中定义的符号;
size 列出目标文件中节的名字和大小;
readelf 显示一个目标文件的完整结构,包括ELF 头中编码的所有信息。
objdump 显示目标文件的所有信息,最有用的功能是反汇编.text节中的二进制指令。
ldd 列出可执行文件在运行时需要的共享库。

动态查看进程调用命令

ltrace 跟踪进程调用库函数过程 strace 系统调用的追踪或信号产生的情况 Relyze 图形化收费试用

debugging symbols

  • 编译时加入-g选项,可以生成调试信息,这样在gdb中可以查看源代码。
  • 但是在复杂的编译过程中,最后可执行文件丢失了debugging symbols,所以研究一下怎么生成debugging symbols, 编译过程中的传递,以及如何查看。

debugging symbols的内容

objdump -g <archive_file>.a
# 如果.o文件有debugging symbols,会输出各section详细信息
Contents of the .debug_aranges section (loaded from predict-c.o):
# 没有则如下
cabac-a.o:     file format elf64-x86-64

dct-a.o:     file format elf64-x86-64

deblock-a.o:     file format elf64-x86-64

生成debugging symbols

  • 预处理过程
  • 应该会保留debugging symbols所需的信息,在实验后发现,执行gcc -E -g testBigExe.cpp -o testDebug.i相对于无-g的命令,只会多一行信息# 1 "/staff/shaojiemike/test/OS//"
  • 编译过程
  • 执行gcc -S -g testBigExe.cpp -o testDebug.s,对比之前的汇编文件,由72行变成9760行。具体解析参考 GNU assembly file一文
  • -g前后
  • 汇编过程:保留了debug信息的汇编代码生成带debug信息的目标文件
  • 链接(Linker)

编译代码中OpenMP实现

简单的#pragma omp for,编译后多出汇编代码如下。当前可以创建多少个线程默认汇编并没有显示的汇编指令。

call omp_get_num_threads@PLT
movl %eax, %ebx
call omp_get_thread_num@PLT
movl %eax, %ecx

call    GOMP_barrier@PLT

某些atomic的导语会变成对应汇编

需要进一步的研究学习

  • chatGPT说:后端阶段(例如汇编器和连接器),则主要是对汇编代码和目标代码进行优化,例如指令调度、地址计算、代码缩减等。但是我持严重怀疑态度, 链接过程有这么多优化吗?

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

基础不牢,地动山摇。ya 了。

参考文献

https://www.cnblogs.com/LiuYanYGZ/p/5574601.html

https://hansimov.gitbook.io/csapp/part2/ch07-linking/7.5-symbols-and-symbol-tables

C Program Compile Problems

TODO:[5] please fix all in free time

Overview

版本冲突的几个主要原因:

  1. 老项目会自己开发New func & macro/definitions
  2. New sys-header also develop new func & macro/definitions
  3. Conflict1: redefinition of the same macro/definitions. 大家都需要某个功能,老项目原本自己写了这部分代码。随着时间的发展 merge 到了sys-file。old project compile in new sys with the diff code conflict for same purpose.
    1. FIX: delete the old project code, and use the sys STL
  4. Conflict2: New sys develop a new func(e.g., __THROW) in it's header files, part sys header(e.g, iostream) is included by old project, but the definition of new func is not included for lower search priority.
    1. FIX: update old project to support the new func (because you can not degrade the sys-files).

tricks

  1. find definition xxx in header search path
ag -us "def.*xxx" /usr/include /usr/lib/gcc/x86_64-linux-gnu/11/include /usr/local/include
  1. After each updates(code changing), you should guarantee the benchmark(official makefile/simple testcase) compile maintain the correct compilation.

1

compile multipim with pin3.28 under g++11

/usr/include/x86_64-linux-gnu/c++/11/bits/os_defines.h:44:19: error: missing binary operator before token "(" 
4| #if __GLIBC_PREREQ(2,15) && defined(_GNU_SOURCE)
  1. SO(StackOverflow) show similar errors, I guess the Gabriel Devillers answer the key point, self head file's higher priority lead to the no define of __GLIBC_PREREQ()
  2. I successfully found head file in directory PIN/extras/crt/include/features.h
  3. fix by remove or adjust the head file searching order?
    1. just rename to feature_bk.h

1.1

In file included from pin/extras/crt/include/sys/cdefs.h:84,
                 from pin/extras/crt/include/features.h:33,
                 from pin/extras/cxx/include/__config:228,
                 from pin/extras/cxx/include/string:510,
                 from build/opt/g_std/g_string.h:31,
                 from build/opt/access_tracing.h:29,
                 from build/opt/access_tracing.cpp:26:
/usr/include/x86_64-linux-gnu/sys/cdefs.h:146:55: error: missing binary operator before token "("
  146 | #if __USE_FORTIFY_LEVEL == 3 && (__glibc_clang_prereq (9, 0)                  \
      |                                                       ^     

We find the definition

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0 on git:main x [16:45:13] C:2
$ ag -us "__glibc_clang_prereq" /usr/include ./pin ./common ./build/opt /usr/lib/gcc/x86_64-linux-gnu/11/include /usr/local/include
/usr/include/features.h
179:# define __glibc_clang_prereq(maj, min) \
182:# define __glibc_clang_prereq(maj, min) 0
413:# elif _FORTIFY_SOURCE > 2 && (__glibc_clang_prereq (9, 0)                \

TODO: maybe include_next in pin/extras/crt/include/features.h

2

during the officail pintool compilation of pin2.14 under g++11

#if !defined(__GXX_ABI_VERSION) || CC_USED_ABI_VERSION != __GXX_ABI_VERSION
    #error The C++ ABI of your compiler does not match the ABI of the pin kit.
#endif

TODO:

3

compile multipim with pin3.28 under g++11 after adding crt include path.

In file included from /usr/include/c++/11/bits/localefwd.h:40,                                                 
                from /usr/include/c++/11/string:43,                                                           
                from build/opt/g_std/g_string.h:31,                                                           
                from build/opt/access_tracing.h:29,                                                           
                from build/opt/access_tracing.cpp:26:                                        
/usr/include/x86_64-linux-gnu/c++/11/bits/c++locale.h: In function 'int std::__convert_from_v(const __c_locale&, char*, int, const char*, ...)':                                            
/usr/include/x86_64-linux-gnu/c++/11/bits/c++locale.h:75:16: error: variable 'std::__c_locale __old' has initializer but incomplete type
   75 |     __c_locale __old = __gnu_cxx::__uselocale(__cloc);                                      
      |                ^~~~~
/usr/include/x86_64-linux-gnu/c++/11/bits/c++locale.h:75:47: error: cannot convert 'const __c_locale' to 'locale_t' {aka '__locale_t*'}
75 |     __c_locale __old = __gnu_cxx::__uselocale(__cloc);                                      
   |                                               ^~~~~~                                        
   |                                               |                                             
   |                                               const __c_locale                        
   /usr/include/x86_64-linux-gnu/c++/11/bits/c++locale.h:52:34: note:   initializing argument 1 of '__locale_t* __gnu_cxx::__uselocale(locale_t)'                                                 
   52 |   extern "C" __typeof(uselocale) __uselocale;                                               
      |                                  ^~~~~~~~~~~          

error: initializer but incomplete type is caused by compiler-think undefined type.

But the define is just before lines typedef __locale_t __c_locale;

And found the conflict defined

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0/pin on git:main x [9:56:26]
$ ag __locale_t
extras/crt/include/xlocale.h
33:struct __locale_t;
34:typedef struct __locale_t* locale_t;

replace these two lines to std header file

struct __locale_struct
{
  /* Note: LC_ALL is not a valid index into this array.  */
  struct __locale_data *__locales[13]; /* 13 = __LC_LAST. */

  /* To increase the speed of this solution we add some special members.  */
  const unsigned short int *__ctype_b;
  const int *__ctype_tolower;
  const int *__ctype_toupper;

  /* Note: LC_ALL is not a valid index into this array.  */
  const char *__names[13];
};

typedef struct __locale_struct *__locale_t;
typedef __locale_t locale_t;

4

In file included from /usr/include/c++/11/cstdlib:75,
                 from /usr/include/c++/11/ext/string_conversions.h:41,
                 from /usr/include/c++/11/bits/basic_string.h:6608,
                 from /usr/include/c++/11/string:55,
                 from build/opt/g_std/g_string.h:31,
                 from build/opt/access_tracing.h:29,
                 from build/opt/access_tracing.cpp:26:
/usr/include/stdlib.h:97: note: this is the location of the previous definition
   97 | #define MB_CUR_MAX      (__ctype_get_mb_cur_max ())
      | 
/usr/include/stdlib.h:98:45: error: expected initializer before '__THROW'
   98 | extern size_t __ctype_get_mb_cur_max (void) __THROW __wur;
      |                                             ^~~~~~~

This shows error of the definition the __THROW. Ensure that the __THROW macro is correctly defined or included in your code.

$ ag -us "def.*__THROW" /usr/include ./pin ./common ./build/opt /usr/lib/gcc/x86_64-linux-gnu/11/include /usr/local/include
/usr/include/x86_64-linux-gnu/sys/cdefs.h
79:#  define __THROW    __attribute__ ((__nothrow__ __LEAF))
80:#  define __THROWNL  __attribute__ ((__nothrow__))
86:#    define __THROW  noexcept (true)
88:#    define __THROW  throw ()

So __THROW is defined in /usr/include/x86_64-linux-gnu/sys/cdefs.h

Stupid idea is to sudo vim the sys-header stdlib.h add codes:

/* TSJ 231009: add for the miss of definition of __THROW */
#include <x86_64-linux-gnu/sys/cdefs.h>

4.1

In file included from build/opt/pin_cmd.cpp:30:
/usr/include/wordexp.h:66:45: error: expected initializer before '__THROW'
   66 | extern void wordfree (wordexp_t *__wordexp) __THROW;
      |                                             ^~~~~~~

# wordexp.h -> <features.h> -> sys/cdefs.h

The more likely scenario is pin-def cdefs.h lack the definition of macro __THROW

$ ag -usg "cdefs\.h" /usr/include ./pin ./common ./build/opt /usr/lib/gcc/x86_64-linux-gnu/11/include /usr/local/include
/usr/include/bsd/sys/cdefs.h
/usr/include/x86_64-linux-gnu/sys/cdefs.h
pin/extras/crt/include/sys/cdefs.h

It's a bad idea to include_next which lead to 7.1 error.

The easy solution is to degrade the wordexp.h with remove the only one macro __THROW.

5

In file included from pin/source/include/pin/pin.H:27,
                 from build/opt/decoder.h:31,
                 from build/opt/core.h:30,
                 from build/opt/ooo_core.h:34,
                 from build/opt/contention_sim.cpp:35:
pin/extras/components/include/util/intel-fp.hpp: At global scope:
pin/extras/components/include/util/intel-fp.hpp:21:9: error: 'UINT64' does not name a type; did you mean 'UINT64_C'?
   21 |         UINT64 _significand; ///< The floating-point significand.
      |         ^~~~~~
      |         UINT64_C

Type is not defined, include to fix

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0 on git:main x [16:16:38]                 
$ ag ' UINT64;'                                                                               
pin/extras/crt/include/types.h                                                                
70:typedef unsigned __int64 UINT64;                                                           
86:typedef uint64_t UINT64;    

5.1

In file included from build/opt/debug_zsim.cpp:28:
/usr/include/gelf.h:70:9: error: 'Elf64_Section' does not name a type; did you mean 'Elf64_Ssize'?
   70 | typedef Elf64_Section GElf_Section;
      |         ^~~~~~~~~~~~~
      |         Elf64_Ssize

We found the definition

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0 on git:main x [15:57:57] C:2
$ ag -us "Elf64_Section" /usr/include ./pin ./common ./build/opt /usr/lib/gcc/x86_64-linux-gnu/11/include /usr/local/include
/usr/include/gelf.h
70:typedef Elf64_Section GElf_Section;

/usr/include/elf.h
52:typedef uint16_t Elf64_Section;
532:  Elf64_Section     st_shndx;               /* Section index */

the reason is header higher search priority

$ ag -usg "elf.h" /usr/include ./pin ./common ./build/opt /usr/lib/gcc/x86_64-linux-gnu/11/include /usr/local/include
/usr/include/gelf.h
/usr/include/elf.h
pin/extras/crt/include/elf.h
pin/extras/crt/include/freebsd/3rd-party/sys/x86/include/elf.h # used by pin/extras/crt/include/elf.h

We can not just add #include_next in pin's elf.h due the redefined of many structs.

We fix it by add missing macro definitions in suitable header file pin/extras/crt/include/freebsd/3rd-party/include/elf.h

/* Type for section indices, which are 16-bit quantities.  */
typedef uint16_t Elf32_Section;
typedef uint16_t Elf64_Section;

6

In file included from pin/source/include/pin/pin.H:96,
                 from build/opt/decoder.h:32,
                 from build/opt/decoder.cpp:26:
pin/source/include/pin/gen/ins_api_xed_ia32.PH:65:27: error: reference to 'USIZE' is ambiguous
   65 | extern PIN_DEPRECATED_API USIZE INS_MemoryWriteSize(INS ins);
      |                           ^~~~~

conflict define in current path header and search path header, select one to comment.

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0 on git:main x [16:39:02]
$ ag ' USIZE;' pin
pin/extras/crt/include/types.h
122:typedef ADDRINT USIZE;
pin/source/include/pin/gen/types_foundation.PH
41:typedef unsigned int USIZE;

6.1

In file included from build/opt/slab_alloc.h:43,
                 from build/opt/event_recorder.h:32,
                 from build/opt/cache.cpp:29:
build/opt/mutex.h: At global scope:
build/opt/mutex.h:56:30: error: reference to 'mutex' is ambiguous
   56 | class aligned_mutex : public mutex {} ATTR_LINE_ALIGNED;
      |                              ^~~~~
In file included from pin/extras/cxx/include/mutex:190,
                 from pin/extras/cxx/include/__locale:18,
                 from pin/extras/cxx/include/ios:215,
                 from pin/extras/cxx/include/iostream:37,
                 from build/opt/memory_hierarchy.h:32,
                 from build/opt/cache_arrays.h:29,
                 from build/opt/cache.h:29,
                 from build/opt/cache.cpp:26:
pin/extras/cxx/include/__mutex_base:32:78: note: candidates are: 'class std::__1::mutex'
   32 | class _LIBCPP_TYPE_VIS _LIBCPP_THREAD_SAFETY_ANNOTATION(capability("mutex")) mutex
      |                                                                              ^~~~~
In file included from build/opt/slab_alloc.h:43,
                 from build/opt/event_recorder.h:32,
                 from build/opt/cache.cpp:29:
build/opt/mutex.h:34:7: note:                 'class mutex'
   34 | class mutex : public GlobAlloc {
      |       ^~~~~

It seems the class redefine, we comment pintool part code to fix it.

7

In file included from pin/extras/cxx/include/mutex:190,
                 from pin/extras/cxx/include/__locale:18,
                 from pin/extras/cxx/include/ios:215,
                 from pin/extras/cxx/include/iostream:37,
                 from build/opt/memory_hierarchy.h:32,
                 from build/opt/access_tracing.h:30,
                 from build/opt/access_tracing.cpp:26:
pin/extras/cxx/include/__mutex_base: In member function 'void std::__1::condition_variable::__do_timed_wait(std::__1::unique_lock<std::__1::mutex>&, std::__1::chrono::time_point<std::__1::chrono::steady_clock, std::__1::chrono::duration<long long int, std::__1::ratio<1, 1000000000> > >)':
pin/extras/cxx/include/__mutex_base:508:16: error: 'pthread_cond_clockwait' was not declared in this scope; did you mean 'pthread_cond_wait'?
  508 |     int __ec = pthread_cond_clockwait(&__cv_, __lk.mutex()->native_handle(), CLOCK_MONOTONIC, &__ts);
      |                ^~~~~~~~~~~~~~~~~~~~~~
      |                pthread_cond_wait

After Reading header file, It seem compiler believe system contain the function.

TODO: 阅读system header file 的include和底层代码实现: 线程,print,syscall。e.g., pthread_cond_clockwait function is defined where

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0 on git:main x [15:10:32]
$ ag pthread_cond_clockwait /usr/include
/usr/include/pthread.h
1171:extern int pthread_cond_clockwait (pthread_cond_t *__restrict __cond,

Of course,We assume pin/extras/crt/include/pthread.h has higher include priority. We will prove it in the later.

# the exsit include
$ ag pthread.h  pin/extras/cxx
pin/extras/cxx/include/__threading_support
32:# include <pthread.h>
# So who include __threading_support
$ ag __threading_support pin/extras/cxx
pin/extras/cxx/include/__mutex_base # the error header
16:#include <__threading_support>

Reading __threading_support code, the include under a #ifdef condition

#if defined(_LIBCPP_HAS_THREAD_API_PTHREAD)
# include <pthread.h>
...
#elif defined(_LIBCPP_HAS_THREAD_API_C11)
# include <threads.h>
#endif

So which file define _LIBCPP_HAS_THREAD_API_PTHREAD

$ ag -u _LIBCPP_HAS_THREAD_API_PTHREAD

pin/extras/cxx/include/__config
1134:    !defined(_LIBCPP_HAS_THREAD_API_PTHREAD) && \
1149:#    define _LIBCPP_HAS_THREAD_API_PTHREAD
1152:#    define _LIBCPP_HAS_THREAD_API_PTHREAD
1160:#if defined(_LIBCPP_HAS_THREAD_API_PTHREAD)
1170:#if defined(_LIBCPP_HAS_NO_THREADS) && defined(_LIBCPP_HAS_THREAD_API_PTHREAD)
1171:#error _LIBCPP_HAS_THREAD_API_PTHREAD may only be defined when \
1199:#if (defined(_LIBCPP_HAS_THREAD_API_PTHREAD) && defined(__GLIBC__)) \


# second one define it
# and find #include <__config> in the error header

According to -MMD option's result .d file, we make sure include the pin/extras/cxx/include/__config one. And add #warning preprocessor directive or check -MD option result to make sure include <pthread.h>. But the point is include pin/extras/crt/include/pthread.h

First Idea: I believe that changing the potentially buggy Pin crt code is a bad idea. The more likely scenario is that we are using the crt incorrectly. So, let's identify how to trigger the bug starting from build/opt/memory_hierarchy.h:32, and then replicate it in a simple pintool to verify whether we are indeed using it incorrectly.

But the real situation is pintool just #include <iostream> and the simple pintool also include without compilation error. Weird scenario deserves more research.

7.1

# pass
# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0/pin/source/tools/ManualExamples on git:main x [10:02:19]
$  g++ -Wall -Werror -Wno-unknown-pragmas -DPIN_CRT=1 -fno-stack-protector -DTARGET_IA32E -DHOST_IA32E -fPIC -DTARGET_LINUX -fabi-version=2 -I../../../sou
rce/include/pin -I../../../source/include/pin/gen -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/cxx/include -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/arch-x86_64 -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi/asm-x86 -I../../../extras/components/include -I../../../extras/xed-int
el64/include/xed -I../../../source/tools/Utils -I../../../source/tools/InstLib -O3 -fomit-frame-pointer -MD -c inscount0.cpp

# failed
# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0/pin/source/tools/ManualExamples on git:main x [10:02:53]
$  g++ -Wall -Werror -Wno-unknown-pragmas -DPIN_CRT=1 -fno-stack-protector -DTARGET_IA32E -DHOST_IA32E -fPIC -DTARGET_LINUX -fabi-version=2 -I../../../sou
rce/include/pin -I../../../source/include/pin/gen -isystem /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include -isystem /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/crt/include -isystem /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/crt/include/arch-x86_64 -isystem /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/crt/include/kernel/uapi -isystem /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/crt/include/kernel/uapi/asm-x86 -I../../../extras/components/include -I../../../extras/xed-intel64/include/xed -I../../../source/tools/Utils -I../../../source/tools/InstLib -O3 -fomit-frame-pointer -MD -c inscount0.cpp
In file included from /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include/mutex:190,
                 from /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include/__locale:18,
                 from /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include/ios:215,
                 from /staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include/iostream:37,
                 from inscount0.cpp:6:
/staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include/__mutex_base: In member function 'void std::__1::condition_variable::__do_timed_wait(std::__1::unique_lock<std::__1::mutex>&, std::__1::chrono::time_point<std::__1::chrono::steady_clock, std::__1::chrono::duration<long long int, std::__1::ratio<1, 1000000000> > >)':
/staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include/__mutex_base:508:16: error: 'pthread_cond_clockwait' was not declared in this scope; did you mean 'pthread_cond_wait'?
  508 |     int __ec = pthread_cond_clockwait(&__cv_, __lk.mutex()->native_handle(), CLOCK_MONOTONIC, &__ts);
      |                ^~~~~~~~~~~~~~~~~~~~~~
      |                pthread_cond_wait

After the compile compare, we decide the error is due to our change in pin code.

But we just change a little.

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0 on git:main x [10:09:58]                                                                                                                                                                        
$ diff /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux pin 
diff '--color=auto' -r /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/features.h pin/extras/crt/include/features.h
32a33
> #include_next <features.h>
diff '--color=auto' -r /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/freebsd/3rd-party/include/elf.h pin/extras/crt/include/freebsd/3rd-party/include/elf.h
42a43,46
> /* Type for section indices, which are 16-bit quantities.  */
> typedef uint16_t Elf32_Section;
> typedef uint16_t Elf64_Section;
> 
diff '--color=auto' -r /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/sys/cdefs.h pin/extras/crt/include/sys/cdefs.h
44a45
> 
82a84,114
> #include_next <sys/cdefs.h>
> 
> 
> /* GCC can always grok prototypes.  For C++ programs we add throw()
>    to help it optimize the function calls.  But this only works with
>    gcc 2.8.x and egcs.  For gcc 3.4 and up we even mark C functions
>    as non-throwing using a function attribute since programs can use
>    the -fexceptions options for C code as well.  */
> // # if !defined __cplusplus \
> //      && (__GNUC_PREREQ (3, 4) || __glibc_has_attribute (__nothrow__))
> // #  define __THROW  __attribute__ ((__nothrow__ __LEAF))
> // #  define __THROWNL    __attribute__ ((__nothrow__))
> // #  define __NTH(fct)   __attribute__ ((__nothrow__ __LEAF)) fct
> // #  define __NTHNL(fct)  __attribute__ ((__nothrow__)) fct
> // # else
> // #  if defined __cplusplus && (__GNUC_PREREQ (2,8) || __clang_major >= 4)
> // #   if __cplusplus >= 201103L
> // #    define __THROW    noexcept (true)
> // #   else
> // #    define __THROW    throw ()
> // #   endif
> // #   define __THROWNL   __THROW
> // #   define __NTH(fct)  __LEAF_ATTR fct __THROW
> // #   define __NTHNL(fct) fct __THROW
> // #  else
> // #   define __THROW
> // #   define __THROWNL
> // #   define __NTH(fct)  fct
> // #   define __NTHNL(fct) fct
> // #  endif
> // # endif

Rollback two include_next to pass the benchmark.

8

In file included from pin/extras/cxx/include/limits.h:57,
                 from pin/extras/cxx/include/climits:41,
                 from pin/extras/cxx/include/ratio:82,
                 from pin/extras/cxx/include/chrono:830,
                 from pin/extras/cxx/include/__threading_support:15,
                 from pin/extras/cxx/include/atomic:579,
                 from pin/extras/cxx/include/memory:687,
                 from pin/extras/cxx/include/algorithm:653,
                 from pin/extras/cxx/include/__string:57,
                 from pin/extras/cxx/include/string_view:179,
                 from pin/extras/cxx/include/string:511,
                 from build/opt/g_std/g_string.h:31,
                 from build/opt/access_tracing.h:29,
                 from build/opt/access_tracing.cpp:26:
build/opt/common/global_const.h:32:16: error: expected unqualified-id before numeric constant
   32 | const unsigned PAGE_SIZE=(1UL<<PAGE_SHIFT);
      |                ^~~~~~~~~

Ref suggest us to test the macro define

In file included from build/opt/common/common_structures.h:20,
                 from build/opt/memory_hierarchy.h:39,
                 from build/opt/access_tracing.h:30,
                 from build/opt/access_tracing.cpp:26:
build/opt/common/global_const.h:33:2: error: #error "PAGE_SIZE is defined"
   33 | #error "PAGE_SIZE is defined"
      |  ^~~~~

and we find the redefined in

# shaojiemike @ icarus0 in ~/github/MultiPIM_icarus0 on git:main x [10:28:46]
$ ag -u "define PAGE_SIZE"
pin/extras/crt/include/limits.h
124:#define PAGE_SIZE 4096
pin/extras/crt/include/kernel/uapi/linux/a.out.h
120:#define PAGE_SIZE 0x400

FIX: set #ifdef in old pintool code.

9 ld

During the zsim-like multipim compilation, dumptrace

g++ -o build/opt/dumptrace -Wl,-R/staff/shaojiemike/github/MultiPIM_icarus0/common/libconfig/lib -L/usr/lib/x86_64-linux-gnu/hdf5/serial/ build/opt/dumptrace.ot build/opt/access_tracing.ot build/opt/memory_hierarchy.ot build/opt/config.ot build/opt/galloc.ot build/opt/log.ot build/opt/pin_cmd.ot -Lcommon/libconfig/lib -lconfig++ -lhdf5 -lhdf5_hl

/usr/bin/ld: build/opt/dumptrace.ot: in function `std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string<decltype(nullptr)>(char const*)':
/staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/cxx/include/string:841: undefined reference to `std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__init(char const*, unsigned long)'

After reading the code, template function init denifition seems no problem. So the first judgement is this is due to the new pin lib missing.

9.1 : crude solution

Copy all missing lib from inscount0.so

g++ -o build/opt/dumptrace -Wl,--hash-style=sysv /staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/runtime/pincrt/crtbeginS.o -Wl,-Bsymbolic -Wl,--version-script=/staff/shaojiemike/github/MultiPIM_icarus0/pin/source/include/pin/pintool.ver -fabi-version=2 -Wl,-R/staff/shaojiemike/github/MultiPIM_icarus0/common/libconfig/lib -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/runtime/pincrt -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/lib -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/lib-ext -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/xed-intel64/lib -L/usr/lib/x86_64-linux-gnu/hdf5/serial/ build/opt/dumptrace.ot build/opt/access_tracing.ot build/opt/memory_hierarchy.ot build/opt/config.ot build/opt/galloc.ot build/opt/log.ot build/opt/pin_cmd.ot -Lcommon/libconfig/lib -lconfig++ -lhdf5 -lhdf5_hl -lpin -lxed /staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/runtime/pincrt/crtendS.o -lpindwarf -ldl-dynamic -nostdlib -lc++ -lc++abi -lm-dynamic -lc-dynamic -lunwind-dynamic

which lead error

/usr/bin/ld: build/opt/config.ot: undefined reference to symbol '__cxa_get_exception_ptr@@CXXABI_1.3.1'
/usr/bin/ld: /lib/x86_64-linux-gnu/libstdc++.so.6: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

forget add -shared option to eliminate the error msg, but the I guess build/opt/dumptrace is an excutable, because the code

int main(int argc, const char* argv[]) {
    InitLog(""); //no log header
    if (argc != 2) {
        info("Prints an access trace");
        info("Usage: %s <trace>", argv[0]);
        exit(1);
    }
    ...}

9.2 pick in details

dumptrace is executable and inscount0.so is a dynamic library. We should analyse the lack part.

First we try to delete part and lower pin-lib priority

g++ -o build/opt/dumptrace -Wl,-R/staff/shaojiemike/github/MultiPIM_icarus0/common/libconfig/lib -L/usr/lib/x86_64-linux-gnu/hdf5/serial/ build/opt/dumptrace.ot build/opt/access_tracing.ot build/opt/memory_hierarchy.ot build/opt/config.ot build/opt/galloc.ot build/opt/log.ot build/opt/pin_cmd.ot -Lcommon/libconfig/lib -lconfig++ -lhdf5 -lhdf5_hl  -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/runtime/pincrt -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/lib -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/lib-ext -L/staff/shaojiemike/github/MultiPIM_icarus0/pin/extras/xed-intel64/lib -lpin -lxed -lpindwarf -ldl-dynamic -nostdlib -lc++ -lc++abi -lm-dynamic -lc-dynamic -lunwind-dynamic

which alse error with __cxa_get_exception_ptr@@CXXABI_1.3.1 and error adding symbols: DSO missing from command line

TODO: ???

9.3

/usr/bin/ld: build/opt/dumptrace.ot: in function `AccessTraceReader::read()':
/staff/shaojiemike/github/MultiPIM_icarus0/build/opt/access_tracing.h:70: undefined reference to `__assert2'

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

Old Pintool Upgrade with newest pin

编译Old Pintool with newest pin

常见的问题:

  1. crt 相关的头文件的使用
  2. USIZE不再被定义

主要原因是头文件的include的使用不同,还有一些接口的改变。

分析基于inscount0.so的simple test pintool的make流程

$ make obj-intel64/inscount0.so
g++ 
# Warning Options
-Wall -Werror -Wno-unknown-pragmas -Wno-dangling-pointer 
# Program Instrumentation Options
-fno-stack-protector
# Code-Gen-Options
-fno-exceptions -funwind-tables -fasynchronous-unwind-tables -fPIC
# C++ Dialect
-fabi-version=2 -faligned-new -fno-rtti
# define
-DPIN_CRT=1 -DTARGET_IA32E -DHOST_IA32E -DTARGET_LINUX 
# include
-I../../../source/include/pin 
-I../../../source/include/pin/gen 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/cxx/include 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/arch-x86_64 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi 
-isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi/asm-x86 
-I../../../extras/components/include 
-I../../../extras/xed-intel64/include/xed 
-I../../../source/tools/Utils 
-I../../../source/tools/InstLib 
# Optimization Options
-O3 -fomit-frame-pointer -fno-strict-aliasing 
-c -o obj-intel64/inscount0.o inscount0.cpp

g++ -shared -Wl,--hash-style=sysv ../../../intel64/runtime/pincrt/crtbeginS.o -Wl,-Bsymbolic -Wl,--version-script=../../../source/include/pin/pintool.ver -fabi-version=2  
 -o obj-intel64/inscount0.so obj-intel64/inscount0.o
  -L../../../intel64/runtime/pincrt 
  -L../../../intel64/lib 
  -L../../../intel64/lib-ext 
  -L../../../extras/xed-intel64/lib 
  -lpin -lxed ../../../intel64/runtime/pincrt/crtendS.o -lpindwarf -ldl-dynamic -nostdlib -lc++ -lc++abi -lm-dynamic -lc-dynamic -lunwind-dynamic

对应的makefile规则在source/tools/Config/makefile.default.rules

# Build the intermediate object file.
$(OBJDIR)%$(OBJ_SUFFIX): %.cpp
    $(CXX) $(TOOL_CXXFLAGS) $(COMP_OBJ)$@ $<
# Build the tool as a dll (shared object).
$(OBJDIR)%$(PINTOOL_SUFFIX): $(OBJDIR)%$(OBJ_SUFFIX)
    $(LINKER) $(TOOL_LDFLAGS) $(LINK_EXE)$@ $< $(TOOL_LPATHS) $(TOOL_LIBS)   
  1. how to solve the UINT64 undefined bug: inscount0.cpp include pin.H which includes types_foundation.PH

与基于 scons的编译流程的对比

由于old pintool 基于 pin2.14。作为对比也分析inscount0.so的编译过程

g++ 
# Warning Options
-Wall -Werror -Wno-unknown-pragmas
# Program Instrumentation Options
-fno-stack-protector
# Code-Gen-Options
-fPIC
# define
-DBIGARRAY_MULTIPLIER=1 -DTARGET_IA32E -DHOST_IA32E -DTARGET_LINUX 
-I../../../source/include/pin 
-I../../../source/include/pin/gen 
-I../../../extras/components/include 
-I../../../extras/xed-intel64/include 
-I../../../source/tools/InstLib 
# Optimization Options
-O3 -fomit-frame-pointer -fno-strict-aliasing  
-c -o obj-intel64/inscount0.o inscount0.cpp

同时multipim 的scons的编译细节如下,去除与pin无关的参数:

g++
# Warning Options
-Wall -Wno-unknown-pragmas 
# c++ language
-std=c++0x 
# Code-Gen-Options
 -fPIC 
# debug
 -g  
# Program Instrumentation Options
 -fno-stack-protector 
# Preprocessor Options ???TODO:
 -MMD 
# machine-dependent 
-march=core2 
# C++ Dialect
-D_GLIBCXX_USE_CXX11_ABI=0 
-fabi-version=2 
# define 
-DBIGARRAY_MULTIPLIER=1 -DUSING_XED 
-DTARGET_IA32E -DHOST_IA32E -DTARGET_LINUX 
-DPIN_PATH="/staff/shaojiemike/github/MultiPIM_icarus0/pin/intel64/bin/pinbin" -DZSIM_PATH="/staff/shaojiemike/github/MultiPIM_icarus0/build/opt/libzsim.so" -DMT_SAFE_LOG 
-Ipin/extras/xed-intel64/include 
-Ipin/source/include/pin 
-Ipin/source/include/pin/gen 
-Ipin/extras/components/include 
# Optimization Options
-O3 -funroll-loops -fomit-frame-pointer 
-c -o build/opt/simple_core.os build/opt/simple_core.cpp

STEP1: update define and include path order

对比后,pin3.28 相对 pin2.14 编译时,

  • 加入了新定义 -DPIN_CRT=1
  • include path and order changed a lot
  • 编译选项也有改变(low influence)

STEP2: code change for include

// pin/extras/crt/include/freebsd/3rd-party/include/elf.h
> typedef uint16_t Elf32_Section;
> typedef uint16_t Elf64_Section;
// /usr/include/wordexp.h
remove __THROW

STEP3: ld errors

First apply the two change to old pintool

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

CProgramReading

visibility & attribute & capability

#ifndef _LIBCPP_TYPE_VIS
#  if !defined(_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS)
#    define _LIBCPP_TYPE_VIS __attribute__ ((__visibility__("default")))
#  else
#    define _LIBCPP_TYPE_VIS
#  endif
#endif

#ifndef _LIBCPP_THREAD_SAFETY_ANNOTATION
#  ifdef _LIBCPP_HAS_THREAD_SAFETY_ANNOTATIONS
#    define _LIBCPP_THREAD_SAFETY_ANNOTATION(x) __attribute__((x))
#  else
#    define _LIBCPP_THREAD_SAFETY_ANNOTATION(x)
#  endif
#endif  // _LIBCPP_THREAD_SAFETY_ANNOTATION

class _LIBCPP_TYPE_VIS _LIBCPP_THREAD_SAFETY_ANNOTATION(capability("mutex")) mutex
{
}

It's part of code from __mutex_base

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

GCC Compiler Option 2 : Preprocessor Options

-Mxxx

  • -M option is designed for auto-generate Makefile rules from g++ command.
  • 默认包含-E option to STOP after preprocessor during the compilation
  • 默认包含-w option to DISABLE/suppress all warnings.

Using a complex g++ command as an example:

g++ -Wall -Werror -Wno-unknown-pragmas -DPIN_CRT=1 -fno-stack-protector -fno-exceptions -funwind-tables -fasynchronous-unwind-tables -fno-rtti -DTARGET_IA32E -DHOST_IA32E -fPIC -DTARGET_LINUX -fabi-version=2 -faligned-new -I../../../source/include/pin -I../../../source/include/pin/gen -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/cxx/include -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/arch-x86_64 -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi -isystem /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/crt/include/kernel/uapi/asm-x86 -I../../../extras/components/include -I../../../extras/xed-intel64/include/xed -I../../../source/tools/Utils -I../../../source/tools/InstLib -O3 -fomit-frame-pointer -fno-strict-aliasing -Wno-dangling-pointer 
-M inscount0.cpp -o Makefile_bk

In Makefile_bk

inscount0.o: inscount0.cpp \
 # sys header
 /usr/include/stdc-predef.h \
 /staff/shaojiemike/Download/pin-3.28-98749-g6643ecee5-gcc-linux/extras/cxx/include/iostream \
 /usr/lib/gcc/x86_64-linux-gnu/11/include/float.h
 # usr header
 ../../../source/include/pin/pin.H \
 ../../../extras/xed-intel64/include/xed/xed-interface.h \
 ... more header files
  • -MM not include sys header file
  • e.g., the first 3 header will be disapear.
  • -MF filename config the Makefile rules write to which file instead of to stdout.
  • -M -MG is designed to generate Makefile rules when there is header file missing, treated it as generated in normal.
  • -M -MP will generated M-rules for dependency between header files
  • e.g., header1.h includes header2.h. So header1.h: header2.h in Makefile
  • -MD == -M -MF file without default option -E
  • the default file has a suffix of .d, e.g., inscount0.d for -c inscount0.cpp
  • -MMD == -MD not include sys header file

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

C++ ABI

ABI (Application Binary Interface)

According to REF

“ library API + compiler ABI = library ABI ”

  1. library API(Application Programing Interface)
  2. e.g., types/functions/exceptionsSTH in (STL) include files
  3. compiler Application Binary Interface, or ABI
  4. sth during the compilation process, e.g., alignment/funcNameHandling

example:

TODO:

  1. the different version lead to what during the compilation
  2. why we miss this during small C project programming.

ABI Management

We are talking about GNU G++ support

  1. Release versioning on the libstdc++.so binary
  2. e.g, GCC 11.1.0 using libstdc++.so.6.0.29 icarus0 11.4.0 using previous version in document
  3. Symbol versioning on the libstdc++.so binary.
  4. mapfile: libstdc++-v3/config/abi/pre/gnu.ver in github code which shows the detail symbol in diff-verion.
  5. GCC 11.1.0: GLIBCXX_3.4.29, CXXABI_1.3.13

Usage

which verisons can be chosen

TODO: Is there a way to lower the abi verison during the compilation

how to change when compile

The GNU C++ compiler, g++, has a compiler command line option to switch between various different C++ ABIs. This explicit version switch is the flag -fabi-version. In addition, some g++ command line options may change the ABI as a side-effect of use. Such flags include -fpack-struct and -fno-exceptions, but include others: see the complete list in the GCC manual under the heading Options for Code Generation Conventions.

-fabi-version=n | Use version n of the C++ ABI. The default is version 0.(Version 2 is the version of the C++ ABI that first appeared in G++ 3.4, and was the default through G++ 4.9.) ABI: an application binary interface (ABI) is an interface between two binary program modules. Often, one of these modules is a library or operating system facility, and the other is a program that is being run by a user.

check the veriosn a program using

ldd -v /bin/ls show many libc.so.6 in different GLIBC version, chatGPT explain this due to maintain backward compatibility as much as possible to ensure that older programs continue to work on newer systems. This is achieved through versioned symbols and by providing multiple versions of shared libraries.

TODO: If i write a simple C program using DLL, what is the ldd -v print out? will be a simgle verison of libc.so.6? or the equal question is If /bin/ls use diff api in diff versions of DDL.

old program request which version

TODO:

PROBLEM: ABI compability

during the pintool compilation process

#if !defined(__GXX_ABI_VERSION) || CC_USED_ABI_VERSION != __GXX_ABI_VERSION
    #error The C++ ABI of your compiler does not match the ABI of the pin kit.
#endif

TODO:

  1. check the project using which ABI to program
  2. Is there a way to lower the abi verison during the compilation
  3. or compile a program using

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

Intel Pin

简介

Pin 是一个动态二进制插桩工具:

  • 支持 Linux, macOS 和 Windows 操作系统以及可执行程序。
  • Pin可以通过pintools在程序运行期间动态地向可执行文件的任意位置插入任意代码(C/C++),也可以attach到一个正在运行的进程。
  • Pin 提供了丰富的API,可以抽象出底层指令集特性,并允许将进程的寄存器数据等的上下文信息作为参数传递给注入的代码。Pin会自动存储和重置被注入代码覆盖的寄存器,以恢复程序的继续运行。对符号和调试信息也可以设置访问权限。
  • Pin内置了大量的样例插桩工具的源码,包括基本块分析器、缓存模拟器、指令跟踪生成器等,根据自己的实际需求进行自定义开发也十分方便。

特点

  • 只是它的输入不是字节码,而是可执行文件的执行汇编代码(机械码)。
  • Pin 对所有实际执行的代码进行插桩,但是如果指令没有被执行过,就一定不会被插桩。
  • 工作在操作系统之上,所以只能捕获用户级别的指令。
  • 由于Pintool的编译十分自由,Pin不打算提供多线程插桩的支持。
  • 同时有3个程序运行:应用程序本身、Pin、Pintool。
  • 应用程序是被插桩的对象、Pintool包含了如何插桩的规则(用户通过API编写的插入位置和内容)
  • 三者共享同一个地址空间,但不共享库,避免了冲突。 Pintool 可以访问可执行文件的全部数据,还会与可执行文件共享 fd 和其他进程信息。

pin

基本原理

Pin机制类似Just-In-Time (JIT) 编译器,Trace插桩的基本流程(以动态基本块BBLs为分析单位):

  • Pin 会拦截可执行文件的第一条指令,然后对从该指令开始的后续的指令序列重新“compile”新的代码,并执行
  • Pin 在分支退出代码序列时重新获得控制权限,基于分支生成更多的代码,然后继续运行。

动态基本块BBLs

通过一个例子来说明动态基本块BBLs与 汇编代码的BB的区别

switch(i)
{
    case 4: total++;
    case 3: total++;
    case 2: total++;
    case 1: total++;
    case 0:
    default: break;
}

上述代码会编译成下面的汇编, 对于实际执行时跳转从.L7进入的情况,BBLs包括四条指令,但是BB只会包括一条。

.L7:
        addl    $1, -4(%ebp)
.L6:
        addl    $1, -4(%ebp)
.L5:
        addl    $1, -4(%ebp)
.L4:
        addl    $1, -4(%ebp)

Pin会将cpuid, popf and REP prefixed 指令在执行break 成很多BBLs,导致执行的基本块比预想的要多。(主要原因是这些指令有隐式循环,所以Pin会将其拆分成多个BBLs)

Download

  1. Download the kit from Intel website
  2. Because the compatibility problem may you should install pin with archlinux package

Installation

This part is always needed by pintool, for example Zsim, Sniper.

When you meet the following situation, you should consider update your pin version even you can ignore this warning by use flags like -ifeellucky under high compatibility risk.

shaojiemike@snode6 ~/github/ramulator-pim/zsim-ramulator/pin  [08:05:47]
> ./pin
E: 5.4 is not a supported linux release

because this will easily lead to the problem

Pin app terminated abnormally due to signal 6. # or signal 4.

Install pintool(zsim) by reconfig pin version

  1. My first idea is try a compatible pin version (passd a simple test pintool, whatever) instead of the old pin.
  2. Find the suitable simpler pintool can reproduce the situation (old pin failed, but newest pin is passed)
    1. TODO: build(fix pin2.14 CXX_ABI compatibility bug), test suitability
  3. debug the pin tool in details (See in another blog)

插桩粒度

  • Trace instrumentation mode:以动态基本块BBLs为分析单位
  • Instruction instrumentation mode:以指令为分析单位
  • 其余粒度/角度,这些模式通过“缓存”插装请求来实现,因此会产生空间开销,这些模式也被称为预先插装
  • Image instrumentation mode: 通过前提知道需要执行的所有代码,能绘制出全局的插桩情况图
  • Routine instrumentation mode: 理解为函数级插桩,可以对目标程序所调用的函数进行遍历,并在每一个routine中对instruction做更细粒度的遍历。
  • 两者都是在IMG加载时提前插桩,所有不一定routine会被执行。

范围image, section, routine, trace, basic block, instruction的含义和运行时关系

  • image包含section,section包含routine(理由:SEC_Img()RTN_Sec(),后面的静态遍历IMG中指令条数的代码也能说明)
  • routine指的是程序中的函数或子程序,trace指的是程序执行过程中的一条路径,basic block指的是一段连续的指令序列,其中没有跳转指令,instruction指的是程序中的一条指令。
  • 在程序执行时,一个routine可以包含多个trace,一个trace可以包含多个basic block,一个basic block可以包含多个instruction。
for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec))
{
    for (RTN rtn = SEC_RtnHead(sec); RTN_Valid(rtn); rtn = RTN_Next(rtn))
    {
        // Prepare for processing of RTN, an  RTN is not broken up into BBLs,
        // it is merely a sequence of INSs
        RTN_Open(rtn);

        for (INS ins = RTN_InsHead(rtn); INS_Valid(ins); ins = INS_Next(ins))
        {
            count++;
        }

        // to preserve space, release data associated with RTN after we have processed it
        RTN_Close(rtn);
    }
}

API

最重要的是

  • 插桩机制(instrumentation code):插入位置,在什么位置插入什么样的代码
  • 分析代码(analysis code):插入内容,在插桩点执行的代码 (和上一点的区别是什么???)

插桩机制(instrumentation code)

  • TRACE_AddInstrumentFunction Add a function used to instrument at trace granularity
  • BBL粒度插桩相关API
  • INS_AddInstrumentFunction() Add a function used to instrument at instruction granularity
  • 指令粒度插桩相关API
  • IMG_AddInstrumentFunction() Use this to register a call back to catch the loading of an image
  • 插桩不仅可以对每个指令插桩,还可以通过分类筛选后,只对符合要求的指令进行插桩
  • 比如,使用INS_InsertPredicatedCall()

遍历所有的指令

// Forward pass over all instructions in bbl
for( INS ins= BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins) )

// Forward pass over all instructions in routine
for( INS ins= RTN_InsHead(rtn); INS_Valid(ins); ins = INS_Next(ins) )

遍历trace内BBLs

// Visit every basic block  in the trace
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
    // Insert a call to docount before every bbl, passing the number of instructions
    BBL_InsertCall(bbl, IPOINT_BEFORE, (AFUNPTR)docount, IARG_UINT32, BBL_NumIns(bbl), IARG_END);
}

遍历指令里的memOperands

UINT32 memOperands = INS_MemoryOperandCount(ins);

// Iterate over each memory operand of the instruction.
for (UINT32 memOp = 0; memOp < memOperands; memOp++){
    if (INS_MemoryOperandIsRead(ins, memOp)||INS_MemoryOperandIsWritten(ins, memOp)
        //xxx
}

Pintool

最重要的是

  • 插桩机制(instrumentation code):插入位置,在什么位置插入什么样的代码
  • 分析代码(analysis code):插入内容,在插桩点执行的代码 (和上一点的区别是什么???)

Pintool代码

示例分析

// IPOINT_BEFORE 时运行的分析函数
VOID printip(VOID* ip) { fprintf(trace, "%p\n", ip); }

// Pin calls this function every time a new instruction is encountered
VOID InstructionFuc(INS ins, VOID* v)
{
    // Insert a call to printip before every instruction, and pass it the IP
    // IARG_INST_PTR:指令地址 一类的全局变量???
    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)printip, IARG_INST_PTR, IARG_END);
}

int main(int argc, char* argv[])
{
    // Initialize pin
    if (PIN_Init(argc, argv)) return Usage();

    // 登记InstructionFuc为以指令粒度插桩时每条指令触发的函数
    INS_AddInstrumentFunction(InstructionFuc, 0);

    // 登记PrintFuc为程序结束时触发的函数
    PIN_AddFiniFunction(PrintFuc, 0);

    // 部署好触发机制后开始运行程序
    PIN_StartProgram();

    return 0;
}

Debug pintool

目标:以样例插桩工具的源码为对象,熟悉pin的debug流程。

官方教程为例子:

uname -a #intel64
cd source/tools/ManualExamples
# source/tools/Config/makefile.config list all make option
make all OPT=-O0 DEBUG=1 TARGET=intel64 |tee make.log|my_hl
# or just select one: make obj-intel64/inscount0.so
# $(OBJDIR)%$(PINTOOL_SUFFIX) - Default rule for building tools.
#    Example: make obj-intel64/mytool.so

测试运行

../../../pin -t obj-intel64/inscount0.so -- ./a.out #正常统计指令数 to inscount.out

下面介绍Pin 提供的debug工具:

首先创建所需的-gstack-debugger.so和应用fibonacci.exe

cd source/tools/ManualExamples
make OPT=-O0 DEBUG=1 stack-debugger.test

其中OPT=-O0选项来自官方文档Using Fast Call Linkages小节,说明需要OPT=-O0选项来屏蔽makefile中的-fomit-frame-pointer选项,使得GDB能正常显示stack trace(函数堆栈?)

Debug application in Pin JIT mode

$ ../../../pin -appdebug -t obj-intel64/stack-debugger.so -- obj-intel64/fibonacci.exe 1000
Application stopped until continued from debugger.
Start GDB, then issue this command at the prompt:
  target remote :33030

使用pin的-appdebug选项,在程序第一条指令前暂停,并启动debugger窗口。在另一个窗口里gdb通过pid连接:

$ gdb fibonacci #如果没指定应用obj-intel64/fibonacci.exe
(gdb) target remote :33030 #连接gdb端口
(gdb) file obj-intel64/fibonacci.exe #如果没指定应用, 需要指定程序来加载symbols
(gdb) b main #continue 等正常操作

Pintool添加自定义debug指令

能够在上一小节的debug窗口里,通过自定义debug指令打印自定义程序相关信息(比如当前stack使用大小)

Pintool设置满足条件时break并启动debug窗口

Pintool “stack-debugger” 能够监控每条分配stack空间的指令,并当stack使用达到阈值时stop at a breakpoint。

这功能由两部分代码实现,一个是插桩代码,一个是分析代码。

static VOID Instruction(INS ins, VOID *)
{
    if (!EnableInstrumentation) // ROI(Region of interest)开始插桩测量
        return;

    if (INS_RegWContain(ins, REG_STACK_PTR)) //判断指令是不是会改变stack指针(allocate stack)
    {
        IPOINT where = IPOINT_AFTER;
        if (!INS_IsValidForIpointAfter(ins))
            where = IPOINT_TAKEN_BRANCH; //寻找stack空间判断函数插入位置(指令执行完的位置)。如果不是after, 就是taken branch

        INS_InsertIfCall(ins, where, (AFUNPTR)OnStackChangeIf, IARG_REG_VALUE, REG_STACK_PTR,
            IARG_REG_VALUE, RegTinfo, IARG_END); // 插入OnStackChangeIf函数,如果OnStackChangeIf()返回non-zero, 执行下面的DoBreakpoint函数
        INS_InsertThenCall(ins, where, (AFUNPTR)DoBreakpoint, IARG_CONST_CONTEXT, IARG_THREAD_ID, IARG_END);
    }
}

所需的两个函数的分析代码如下:

static ADDRINT OnStackChangeIf(ADDRINT sp, ADDRINT addrInfo)
{
    TINFO *tinfo = reinterpret_cast<TINFO *>(addrInfo);

    // The stack pointer may go above the base slightly.  (For example, the application's dynamic
    // loader does this briefly during start-up.)
    //
    if (sp > tinfo->_stackBase)
        return 0;

    // Keep track of the maximum stack usage.
    //
    size_t size = tinfo->_stackBase - sp;
    if (size > tinfo->_max) 
        tinfo->_max = size; //更新stack使用大小

    // See if we need to trigger a breakpoint.
    //
    if (BreakOnNewMax && size > tinfo->_maxReported)
        return 1;
    if (BreakOnSize && size >= BreakOnSize)
        return 1;
    return 0;
}

static VOID DoBreakpoint(const CONTEXT *ctxt, THREADID tid)
{
    TINFO *tinfo = reinterpret_cast<TINFO *>(PIN_GetContextReg(ctxt, RegTinfo));

    // Keep track of the maximum reported stack usage for "stackbreak newmax".
    //
    size_t size = tinfo->_stackBase - PIN_GetContextReg(ctxt, REG_STACK_PTR);
    if (size > tinfo->_maxReported)
        tinfo->_maxReported = size;

    ConnectDebugger();  // Ask the user to connect a debugger, if it is not already connected.

    // Construct a string that the debugger will print when it stops.  If a debugger is
    // not connected, no breakpoint is triggered and execution resumes immediately.
    //
    tinfo->_os.str("");
    tinfo->_os << "Thread " << std::dec << tid << " uses " << size << " bytes of stack.";
    PIN_ApplicationBreakpoint(ctxt, tid, FALSE, tinfo->_os.str());
}

OnStackChangeIf函数监控当前的stack使用并判断是否到达阈值。DoBreakpoint函数连接debugger窗口,然后触发breakpoint,并打印相关信息。

也可以使用-appdebug_enable参数,取消在第一条指令前开启GDB窗口的功能,而是在触发如上代码的break时,才开启GDB窗口的连接。

而上述代码中的ConnectDebugger函数实现如下:

static void ConnectDebugger()
{
    if (PIN_GetDebugStatus() != DEBUG_STATUS_UNCONNECTED) //判断是不是已有debugger连接
        return;

    DEBUG_CONNECTION_INFO info;
    if (!PIN_GetDebugConnectionInfo(&info) || info._type != DEBUG_CONNECTION_TYPE_TCP_SERVER) //PIN_GetDebugConnectionInfo()获取GDB所需的tcp连接端口
        return;

    *Output << "Triggered stack-limit breakpoint.\n";
    *Output << "Start GDB and enter this command:\n";
    *Output << "  target remote :" << std::dec << info._tcpServer._tcpPort << "\n";
    *Output << std::flush;

    if (PIN_WaitForDebuggerToConnect(1000*KnobTimeout.Value())) //等待其余GDB窗口的连接
        return;

    *Output << "No debugger attached after " << KnobTimeout.Value() << " seconds.\n";
    *Output << "Resuming application without stopping.\n";
    *Output << std::flush;
}

Tips for Debugging a Pintool

这部分讲述了如何debug Pintool中的问题。(对Pintool的原理也能更了解

为此,pin使用了-pause_tool n 暂停n秒等待gdb连接。

../../../pin -pause_tool 10 -t /staff/shaojiemike/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples/obj-intel64/stack-debugger.so -- obj-intel64/fibonacci.exe 1000
Pausing for 10 seconds to attach to process with pid 3502000
To load the debug info to gdb use:
*****************************************************************
set sysroot /not/existing/dir
file
add-symbol-file /staff/shaojiemike/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples/obj-intel64/stack-debugger.so 0x7f3105f24170 -s .data 0x7f31061288a0 -s .bss 0x7f3106129280
*****************************************************************

注意gdb对象既不是pin也不是stack-debugger.so,而是intel64/bin/pinbin。原因是intel64/bin/pinbinpin执行时的核心程序,通过htop监控可以看出。

# shaojiemike @ snode6 in ~/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples on git:dev x [19:57:26]
$ gdb ../../../intel64/bin/pinbin 
(gdb) attach 3502000

这时GDB缺少了stack-debugger.so的调试信息,需要手动添加。这里的add-symbol-file命令是在pin启动时打印出来的,直接复制粘贴即可。

(gdb) add-symbol-file /staff/shaojiemike/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples/obj-intel64/stack-debugger.so 0x7f3105f24170 -s .data 0x7f31061288a0 -s .bss 0x7f3106129280
(gdb) b main #或者 b stack-debugger.cpp:94
gef➤  info b                                                         
Num     Type           Disp Enb Address            What                    
1       breakpoint     keep y   <MULTIPLE>                                  
1.1                         y   0x00000000000f4460 <main>          # 无法访问的地址,需要去除         
1.2                         y   0x00007f3105f36b65 in main(int, char**) at stack-debugger.cpp:94 
(gdb) del 1.1
(gdb) c

个人尝试: 使用VSCODE调试Pintool

  • 想法:VSCODE的GDB也可以attach PID,理论上是可以的
  • 实际问题:VSCODE attach pid后,不会stopAtEntry,只会在已经设置好的断点暂停。但是无法访问到stack-debugger.so的调试信息,无法设置断点。

构建Pintool

  • 首先需要熟悉API
  • PinTool 编译需要自身的 Pin CRT(C RunTime)库,这个库是 Pin 提供的,可以在 Pin 安装目录下找到。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://paper.seebug.org/1742/

https://software.intel.com/sites/landingpage/pintool/docs/98690/Pin/doc/html/index.html#APPDEBUG_UNIX

Zsim

简介

Zsim模拟器是

  • 一个快速的x86-64多核模拟器,它利用英特尔Pin工具包收集进程的内存访问跟踪,并在zsim模拟器中重放跟踪。
  • 一种基于 PIN 的二进制插桩工具,可以在运行时对指令进行插桩,并插入自定义的函数调用,用于模拟内存子系统的行为。
  • 它最初是为了评估ZCache而编写的,但后来它扩展了其功能
  • zsim的主要目标是快速,简单和准确,重点是模拟内存层次结构和大型异构系统