TODO:[5] please fix all in free time
重复了和另一篇
TODO:[5] please fix all in free time
重复了和另一篇
编译是指编译器读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码。
#include语句以及一些宏插入程序文本中,得到main.i和sum.i文件。main.i和sum.i编译成文本文件main.s和sum.c的汇编语言程序。main.s和sum.s翻译成机器语言的二进制指令,并打包成一种叫做可重定位目标程序的格式,并将结果保存在main.o和sum.o两个文件中。这种文件格式就比较接近elf格式了。main.o和sum.o,得到可执行目标文件,就是elf格式文件。

目标文件有三种形式:
.c 文件转化成 .i文件.gcc –E filename.cpp -o filename.i-E Preprocess only; do not compile, assemble or link.-C能保留头文件里的注释,如gcc -E -C circle.c -o circle.cgcc -save-temps -c -o main.o main.ccpp filename.cpp -o filename.i命令linemarkers类似# linenum filename flags的注释,这些注释是为了让编译器能够定位到源文件的行号,以便于编译器能够在编译错误时给出正确的行号。flags meaning除开注释被替换成空格,包括代码里的预处理命令:
#error "text" 的作用是在编译时生成一个错误消息,它会导致编译过程中断。 同理有#warning#define a b 对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。#ifdef SNIPER,#if defined SNIPER && SNIPER == 0,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉-DSNIPER=5#include "FileName"或者#include 等。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
.c/.h或者.i文件转换成.s文件,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 parametercc –S filename.cpp -o filename.s cc1命令-O3)如果想把 C 语言变量的名称作为汇编语言语句中的注释,可以加上 -fverbose-asm 选项:
1 | gcc -S -O3 -fverbose-asm ../src/pivot.c -o pivot_O1.s |
请阅读 GNU assembly file一文
汇编器:将.s 文件转化成 .o文件,
gcc –c,-c Compile and assemble, but do not link.as;
objdump -Sd ../build/bin/pivot > pivot1.s-S 以汇编代码的形式显示C++原程序代码,如果有debug信息,会显示源代码。nm file.o 查看目标文件中的符号表注意,这时候的目标文件里的使用的函数可能没定义,需要链接其他目标文件.a .so .o .dll(Dynamic Link Library的缩写,Windows动态链接库)
List symbol names in object files.
/lib,/usr/lib,/lib64(在64位系统上),/usr/lib64(在64位系统上)遍历 LD_LIBRARY_PATH 中的每个目录,并查找包括软链接在内的所有 .so 文件。
1 | IFS=':' dirs="$LD_LIBRARY_PATH" |
ldconfig 命令用于配置动态链接器的运行时绑定。你可以使用它来查询系统上已知的库文件的位置()。
ldconfig 会扫描
/lib 和 /usr/lib,以及 /etc/ld.so.conf 中列出的目录),查找共享库文件(.so 文件),/etc/ld.so.cache。这个缓存文件会被动态链接器(ld.so 或 ld-linux.so)使用,以加快共享库的查找速度。1 | # 查看所有是path 的库 |
ldd会显示动态库的链接关系,中间的nm为U没关系,只需要最终.so对应符号是T即可。ldd 时避免对不可信的可执行文件运行,因为它可能会执行恶意代码。readelf -d 或 objdump -p 来查看库依赖。通过使用ld命令,将编译好的目标文件连接成一个可执行文件或动态库。
Foo::bar(int,long)会变成bar__3Fooil。其中3是名字字符数见 Linux Executable file: Structure & Running
undefined reference to一旦链接器完成了符号解析这一步,就把代码中的每个符号引用和正好一个符号定义(即它的一个输入目标模块中的一个符号表条目)关联起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小。现在就可以开始重定位步骤了,在这个步骤中,将合并输入模块,并为每个符号分配运行时地址。重定位由两步组成:
.data 节被全部合并成一个节,这个节成为输出的可执行目标文件的.data 节。当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
代码的重定位条目放在 .rel.text 中。已初始化数据的重定位条目放在 .rel.data 中。
下面 展示了 ELF 重定位条目的格式。
R_X86_64_PC32。重定位一个使用 32 位 PC 相对地址的引用。回想一下 3.6.3 节,一个 PC 相对地址就是距程序计数器(PC)的当前运行时值的偏移量。当 CPU 执行一条使用 PC 相对寻址的指令时,它就将在指令中编码的 32 位值加上 PC 的当前运行时值,得到有效地址(如 call 指令的目标),PC 值通常是下一条指令在内存中的地址。(将 PC 压入栈中来使用)R_X86_64_32。重定位一个使用 32 位绝对地址的引用。通过绝对寻址,CPU 直接使用在指令中编码的 32 位值作为有效地址,不需要进一步修改。1 | typedef struct { |
链接器通常从左到右解析依赖项,这意味着如果库 A 依赖于库 B,那么库 B 应该在库 A 之前被链接。
静态库static library就是将相关的目标模块打包形成的单独的文件。使用ar命令。
静态库的优点在于:
问题:
深入理解计算机系统P477,静态库例子
1 | gcc -static -o prog2c main2.o -L. -lvector |
图 7-8 概括了链接器的行为。-static 参数告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,它可以加载到内存并运行,在加载时无须更进一步的链接。-lvector 参数是 libvector.a 的缩写,-L. 参数告诉链接器在当前目录下查找 libvector.a。

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

如上创建了一个可执行目标文件 prog2l,而此文件的形式使得它在运行时可以和 libvector.so 链接。基本的思路是:
dlopen() interface.情况:在应用程序被加载后执行前时,动态链接器加载和链接共享库的情景。
核心思想:由动态链接器接管,加载管理和关闭共享库(比如,如果没有其他共享库还在使用这个共享库,dlclose函数就卸载该共享库。)。
.interp 节,这一节包含动态链接器的路径名,动态链接器本身就是一个共享目标(如在 Linux 系统上的 ld-linux.so). 加载器不会像它通常所做地那样将控制传递给应用,而是加载和运行这个动态链接器。然后,动态链接器通过执行下面的重定位完成链接任务:最后,动态链接器将控制传递给应用程序。从这个时刻开始,共享库的位置就固定了,并且在程序执行的过程中都不会改变。
情况:应用程序在运行时要求动态链接器加载和链接某个共享库,而无需在编译时将那些库链接到应用。
实际应用情况:
思路是将每个生成动态内容的函数打包在共享库中。
编译器yasm的参数-DPIE
如果同一份代码可能被加载到进程空间的任意虚拟地址上执行(如共享库和动态加载代码),那么就需要使用-fPIC生成位置无关代码。
问题:多个进程是如何共享程序的一个副本的呢?
问题。
可以加载而无需重定位的代码称为位置无关代码(Position-Independent Code,PIC)
在一个 x86-64 系统中,对同一个目标模块中符号的引用是不需要特殊处理使之成为 PIC。可以用 PC 相对寻址来编译这些引用,构造目标文件时由静态链接器重定位。
然而,对共享模块定义的外部过程和对全局变量的引用需要一些特殊的技巧,接下来我们会谈到。
解决方法:延迟绑定(lazy binding),将过程地址的绑定推迟到第一次调用该过程时。
动机:使用延迟绑定的动机是对于一个像 libc.so 这样的共享库输出的成百上千个函数中,一个典型的应用程序只会使用其中很少的一部分。把函数地址的解析推迟到它实际被调用的地方,能避免动态链接器在加载时进行成百上千个其实并不需要的重定位。
结果:第一次调用过程的运行时开销很大,但是其后的每次调用都只会花费一条指令和一个间接的内存引用。
实现:延迟绑定是通过两个数据结构之间简洁但又有些复杂的交互来实现的,这两个数据结构是:GOT 和过程链接表(Procedure Linkage Table,PLT)。如果一个目标模块调用定义在共享库中的任何函数,那么它就有自己的 GOT 和 PLT。GOT 是数据段的一部分,而 PLT 是代码段的一部分。
首先,让我们介绍这两个表的内容。
PLT[0] 是一个特殊条目,它跳转到动态链接器中。PLT[1](图中未显示)调用系统启动函数(__libc_start_main),它初始化执行环境,调用 main 函数并处理其返回值从 PLT[2] 开始的条目调用用户代码调用的函数。在我们的例子中,PLT[2] 调用 addvec,PLT[3](图中未显示)调用 printf。
上图a 展示了 GOT 和 PLT 如何协同工作,在 addvec 被第一次调用时,延迟解析它的运行时地址:
上图b 给出的是后续再调用 addvec 时的控制流:
静态库
动态库
1 | shaojiemike@snode6 /lib/modules/5.4.0-107-generic/build [06:32:26] |
加载器:将可执行程序加载到内存并进行执行,loader和ld-linux.so。
将可执行文件加载运行
| 命令 | 描述 |
|---|---|
| ar | 创建静态库,插入、删除、列出和提取成员; |
| stringd | 列出目标文件中所有可以打印的字符串; |
| strip | 从目标文件中删除符号表信息; |
| nm | 列出目标文件符号表中定义的符号; |
| size | 列出目标文件中节的名字和大小; |
| readelf | 显示一个目标文件的完整结构,包括ELF 头中编码的所有信息。 |
| objdump | 显示目标文件的所有信息,最有用的功能是反汇编.text节中的二进制指令。 |
| ldd | 列出可执行文件在运行时需要的共享库。 |
ltrace 跟踪进程调用库函数过程
strace 系统调用的追踪或信号产生的情况
Relyze 图形化收费试用
-g选项,可以生成调试信息,这样在gdb中可以查看源代码。1 | objdump -g <archive_file>.a |
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一文

简单的#pragma omp for,编译后多出汇编代码如下。当前可以创建多少个线程默认汇编并没有显示的汇编指令。
1 | call omp_get_num_threads@PLT |
某些atomic的导语会变成对应汇编
暂无
基础不牢,地动山摇。ya 了。
https://www.cnblogs.com/LiuYanYGZ/p/5574601.html
https://hansimov.gitbook.io/csapp/part2/ch07-linking/7.5-symbols-and-symbol-tables
Old Pintool Upgrade with newest pin
常见的问题:
主要原因是头文件的include的使用不同,还有一些接口的改变。
1 | $ make obj-intel64/inscount0.so |
对应的makefile规则在source/tools/Config/makefile.default.rules
1 | # Build the intermediate object file. |
UINT64 undefined bug: inscount0.cpp include pin.H which includes types_foundation.PH由于old pintool 基于 pin2.14。作为对比也分析inscount0.so的编译过程
1 | g++ |
同时multipim 的scons的编译细节如下,去除与pin无关的参数:
1 | g++ |
对比后,pin3.28 相对 pin2.14 编译时,
-DPIN_CRT=11 | // pin/extras/crt/include/freebsd/3rd-party/include/elf.h |
First apply the two change to old pintool
暂无
暂无
上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。
无
1 | tmux new -t $NAME |
<prefix> + maximizes the current pane to a new window
1 | sudo yum install -y \ |
1 | git clone https://github.com/tmux/tmux.git |
GCC Compiler Option 2 : Preprocessor Options
-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:
1 | 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 |
In Makefile_bk
1 | inscount0.o: inscount0.cpp \ |
-MM not include sys header file-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 filesheader1.h includes header2.h. So header1.h: header2.h in Makefile-MD == -M -MF file without default option -Efile has a suffix of .d, e.g., inscount0.d for -c inscount0.cpp-MMD == -MD not include sys header file暂无
暂无
上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。
无
Pin 是一个动态二进制插桩工具:

Pin机制类似Just-In-Time (JIT) 编译器,Trace插桩的基本流程(以动态基本块BBLs为分析单位):
通过一个例子来说明动态基本块BBLs与 汇编代码的BB的区别
1 | switch(i) |
上述代码会编译成下面的汇编, 对于实际执行时跳转从.L7进入的情况,BBLs包括四条指令,但是BB只会包括一条。
1 | .L7: |
Pin会将cpuid, popf and REP prefixed 指令在执行break 成很多BBLs,导致执行的基本块比预想的要多。(主要原因是这些指令有隐式循环,所以Pin会将其拆分成多个BBLs)
kit from Intel websiteThis 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.
1 | shaojiemike@snode6 ~/github/ramulator-pim/zsim-ramulator/pin [08:05:47] |
because this will easily lead to the problem
1 | Pin app terminated abnormally due to signal 6. # or signal 4. |
PIN_Init之前调用PIN_InitSymbols。1 | for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec)) |
最重要的是
TRACE_AddInstrumentFunction Add a function used to instrument at trace granularityINS_AddInstrumentFunction() Add a function used to instrument at instruction granularityIMG_AddInstrumentFunction() Use this to register a call back to catch the loading of an imageINS_InsertPredicatedCall()1 | // Forward pass over all instructions in bbl |
1 | // Visit every basic block in the trace |
1 | UINT32 memOperands = INS_MemoryOperandCount(ins); |
最重要的是
示例分析
1 | // IPOINT_BEFORE 时运行的分析函数 |
目标:以样例插桩工具的源码为对象,熟悉pin的debug流程。
以官方教程为例子:
1 | uname -a #intel64 |
测试运行
1 | ../../../pin -t obj-intel64/inscount0.so -- ./a.out #正常统计指令数 to inscount.out |
下面介绍Pin 提供的debug工具:
首先创建所需的-g的stack-debugger.so和应用fibonacci.exe
1 | cd source/tools/ManualExamples |
其中OPT=-O0选项来自官方文档Using Fast Call Linkages小节,说明需要OPT=-O0选项来屏蔽makefile中的-fomit-frame-pointer选项,使得GDB能正常显示stack trace(函数堆栈?)
1 | $ ../../../pin -appdebug -t obj-intel64/stack-debugger.so -- obj-intel64/fibonacci.exe 1000 |
使用pin的-appdebug选项,在程序第一条指令前暂停,并启动debugger窗口。在另一个窗口里gdb通过pid连接:
1 | $ gdb fibonacci #如果没指定应用obj-intel64/fibonacci.exe |
能够在上一小节的debug窗口里,通过自定义debug指令打印自定义程序相关信息(比如当前stack使用大小)
Pintool “stack-debugger” 能够监控每条分配stack空间的指令,并当stack使用达到阈值时stop at a breakpoint。
这功能由两部分代码实现,一个是插桩代码,一个是分析代码。
1 | static VOID Instruction(INS ins, VOID *) |
所需的两个函数的分析代码如下:
1 | static ADDRINT OnStackChangeIf(ADDRINT sp, ADDRINT addrInfo) |
OnStackChangeIf函数监控当前的stack使用并判断是否到达阈值。DoBreakpoint函数连接debugger窗口,然后触发breakpoint,并打印相关信息。
也可以使用-appdebug_enable参数,取消在第一条指令前开启GDB窗口的功能,而是在触发如上代码的break时,才开启GDB窗口的连接。
而上述代码中的ConnectDebugger函数实现如下:
1 | static void ConnectDebugger() |
这部分讲述了如何debug Pintool中的问题。(对Pintool的原理也能更了解
为此,pin使用了-pause_tool n 暂停n秒等待gdb连接。
1 | ../../../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 |
注意gdb对象既不是pin也不是stack-debugger.so,而是intel64/bin/pinbin。原因是intel64/bin/pinbin是pin执行时的核心程序,通过htop监控可以看出。
1 | # shaojiemike @ snode6 in ~/github/sniper_PIMProf/pin_kit/source/tools/ManualExamples on git:dev x [19:57:26] |
这时GDB缺少了stack-debugger.so的调试信息,需要手动添加。这里的add-symbol-file命令是在pin启动时打印出来的,直接复制粘贴即可。
1 | (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 |
stack-debugger.so的调试信息,无法设置断点。暂无
暂无
Zsim模拟器是
awesome-shell里多看看。
rg (Fast & Good multi-platform compatibility) > ag > ack(ack-grep) 🔥
1 | # 当前文件夹下查找 dlog关键字 |
find . -name "*xxx*"
lazydocker