为什么同一程序的 INST_PTR(指令指针)值会因不同的运行而改变?

Why does INST_PTR (instruction pointer) values of the same program change for different runs?

在 Intel 的 PinTool 中,您可以使用 IARG_INST_PTRINS_Address 打印出程序中每条指令的 "instruction address"。我观察到 运行 同一程序在不同时间点为完全相同的指令生成不同的指令地址。但是,我希望地址在运行中保持不变。这种变化的根本原因是什么?我在下面附上了两个示例输出,它们显示了执行的前三个指令的操作码和指令地址。

如何找到每条指令的PC?或 OBJDUMPPinTool?

中显示的地址

--RUN1--

op:       MOV addr:0x00007fac87a8d2d0

op: CALL_NEAR addr:0x00007fac87a8d2d3

op:      PUSH addr:0x00007fac87a90a70

--RUN2--

op:       MOV addr:0x00007fc529f402d0

op: CALL_NEAR addr:0x00007fc529f402d3

op:      PUSH addr:0x00007fc529f43a70

因为程序加载到不同的内存地址。

程序被编译为(对于某些部分)使用固定的内存布局;但是,他们不知道将使用哪些地址,因为无法在知道计算机运行时哪些内存地址将空闲的情况下编译它们。

所以它们的内部地址实际上是 "offsets" 来自 "start of program" 内存地址。当程序被复制到 ram 中时,"starting" 内存地址被添加到所有偏移地址。

这也解释了为什么低位是一致的,即使高位不一致。您正在查看同一代码块两次,加载到不同的起始内存地址。这意味着偏移量相同,但地址不同。

(tl;dr 版本最后有一个可能的解决方案。)

这几乎可以肯定是由于 address space randomization 应用于共享库。 运行 多次执行以下命令会让您了解它是如何工作的:

$ cat /proc/self/maps

/proc/self/ 指的是当前进程(打开文件的进程)。还有用于特定 PID 的 /proc// 目录。 maps 文件列出了进程的映射——在本例中是 cat 进程本身。

这是我系统上 运行 的输出:

00400000-0040c000 r-xp 00000000 08:01 3409248            /bin/cat
0060b000-0060c000 r--p 0000b000 08:01 3409248            /bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 3409248            /bin/cat
0063a000-0065b000 rw-p 00000000 00:00 0                  [heap]
7f017ef95000-7f017f761000 r--p 00000000 08:01 8126750    /usr/lib/locale/locale-archive
7f017f761000-7f017f91b000 r-xp 00000000 08:01 11155466   /lib/x86_64-linux-gnu/libc-2.19.so
7f017f91b000-7f017fb1a000 ---p 001ba000 08:01 11155466   /lib/x86_64-linux-gnu/libc-2.19.so
7f017fb1a000-7f017fb1e000 r--p 001b9000 08:01 11155466   /lib/x86_64-linux-gnu/libc-2.19.so
7f017fb1e000-7f017fb20000 rw-p 001bd000 08:01 11155466   /lib/x86_64-linux-gnu/libc-2.19.so
7f017fb20000-7f017fb25000 rw-p 00000000 00:00 0 
7f017fb25000-7f017fb48000 r-xp 00000000 08:01 11155454   /lib/x86_64-linux-gnu/ld-2.19.so
7f017fd1c000-7f017fd1f000 rw-p 00000000 00:00 0 
7f017fd23000-7f017fd47000 rw-p 00000000 00:00 0 
7f017fd47000-7f017fd48000 r--p 00022000 08:01 11155454   /lib/x86_64-linux-gnu/ld-2.19.so
7f017fd48000-7f017fd49000 rw-p 00023000 08:01 11155454   /lib/x86_64-linux-gnu/ld-2.19.so
7f017fd49000-7f017fd4a000 rw-p 00000000 00:00 0 
7fffacef5000-7fffacf16000 rw-p 00000000 00:00 0          [stack]
7fffacf5a000-7fffacf5c000 r-xp 00000000 00:00 0          [vdso]
7fffacf5c000-7fffacf5e000 r--p 00000000 00:00 0          [vvar]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0  [vsyscall]

前三行是您的可执行文件的代码段、只读数据段和读写数据段。其余行是堆栈、堆、共享库的各个段、内存映射文件(作为旁注,库也只是内存映射文件),以及一些与某些系统调用的实现方式相关的内部内容。

如果您重复该命令几次,您可能会看到除了可执行文件中的代码和数据段之外的所有映射随机移动。这是一项安全措施。不知道内存中的内容会使某些攻击更难实现,因为您不能直接跳转到某个您知道会有有用例程的地址。

地址 space 随机化未应用于可执行文件本身的代码和数据段的主要原因可能是效率。未加载到固定地址的代码必须 位置无关 ,这会增加一些开销。这就是为什么需要使用 -fPIC.

显式编译共享库的原因

(除了安全之外,出于其他原因,共享库也需要与位置无关。如果两个库碰巧获得重叠的加载地址,则为每个库使用固定地址会导致问题。)

很遗憾,我对 PinTool 不熟悉。我相信 GDB 只是禁用地址 space 随机化(使用 personality(2) 系统调用)来获取共享库的可预测地址。

地址space随机化可以转off for a single shell session (this seems to use personality() under the hood too), or globally by doing echo 0 > /proc/sys/kernel/randomize_va_space (see the /proc/sys/ documentation).

我在 this page 上找到了以下内容。可能相关。

Does Pin change the application code and data addresses?

...

Note: Recent linux kernels intentionally move the location of stack and dynamically allocated data from run to run, even if you are not using pin. On RedHat-based systems you can workaround this by running Pin as follows:

$ setarch i386 pin -t pintool -- app

tl;博士回答

如果您需要做的只是将恰好来自库的 PinTool 地址关联到 objdump 反汇编地址,并且您不介意每次都做一些手动工作,那么以下应该可以工作:

  1. 从您的进程中打印 /proc/maps。 (你也可以在后台 运行 它并从 shell 打印 /proc//maps,使用例如 $! 得到PID。)

  2. 检查地址属于哪个映射。在库的情况下,它可能是某个库的文本段(在 /proc/maps 中标记为 r-xp)。

  3. 用PinTool中看到的地址减去映射的起始地址

这将为您提供在 objdump 反汇编中看到的地址(当您 运行 它在同一个库中时)。如果库有调试信息,您也可以使用 addr2line(1) 获取源代码行。

当然可能会有更好的工作流程。至少在玩 dlopen(3)dlsym(3) 时,这对我有用。核心转储应该包含库加载地址,所以也许可以以某种方式使用...