【问题标题】:Why does GCC generate a "mov 0x8,%edx" instruction that causes a crash?为什么 GCC 会生成导致崩溃的“mov 0x8,%edx”指令?
【发布时间】:2017-06-12 10:05:46
【问题描述】:

我有一个这样声明的函数:

void
tesysLog(W16 uid, char *file, int line, int level,
         W16 state, W16 event, int error, char *format, ...)

上面还有一个函数会调用tesysLog,例如:

tesysLog(253, __FILE__, __LINE__, 3, 0, 0, result,
        "error(code = %d) is %d instead of %d\n",
        avp->header.code, decoded, size);   

上述调用的相关汇编代码如下:

0x00000000009027e5 <+117>: xor %r9d,%r9d      <---- clear r9d, means argv6 event = 0
0x00000000009027e8 <+120>: mov 0x8,%edx       <---- absolute address, but 0x8 is in reserved segment, crash here

0x00000000009027ef <+127>: xor %r8d,%r8d
0x00000000009027f2 <+130>: mov $0x3,%ecx
0x00000000009027f7 <+135>: mov $0x995600,%esi
0x00000000009027fc <+140>: mov $0xfd,%edi   
0x0000000000902801 <+145>: mov %ebp,0x20(%rsp)
0x0000000000902805 <+149>: mov %eax,0x18(%rsp)
0x0000000000902809 <+153>: xor %eax,%eax
0x000000000090280b <+155>: movq $0x995770,0x8(%rsp)
0x0000000000902814 <+164>: mov %edx,0x10(%rsp)
0x0000000000902818 <+168>: mov $0x8a,%edx
0x000000000090281d <+173>: movl $0xc8e4,(%rsp)
0x0000000000902824 <+180>: callq 0x9136e0 <tesysLog>

我在汇编代码的第二行 mov 0x8,%edx 处收到信号 11,分段错误。看起来这一行是为 tesysLog 调用准备 arg3(int 行)。但是这里,由于使用的是“绝对地址”,并且0x8在进程地址空间的保留段中,依次发出Segmentation fault。

这些代码在 SLES 上运行,并由 gcc 编译。

我想知道为什么要使用“绝对地址”。是 gcc 的 bug,还是有编译选项影响这个?

【问题讨论】:

  • 啊,是的,我稍后在您的汇编输出中看到callq 指令调用tesysLog 函数。你是正确的关于从绝对地址移动。您能否告诉我们您是如何构建代码的,您为 GCC 提供了哪些标志(用于编译和链接),最重要的是尝试创建一个 Minimal, Complete, and Verifiable Example 向我们展示。
  • 我会从我们的客户那里得到这些信息。在此之前,是否有编译选项会影响此行为?喜欢 -fPIC?
  • mov 0x8,%edx 会将常量 8 加载到 edx 中,这根本不会崩溃。你能做一个 objdump 来显示使用的指令,只是为了确保?
  • 您能否向我们展示您的整个程序或从中派生的最小示例?我看不出您发布的 sn-p 是如何导致此错误的,一定是我遗漏了一些东西。
  • 它不是 gcc 认为在 0x08 中的“LINE”,它是压入堆栈的第三个最后一个参数,所以很可能是“header.code”(edx 是 - 短在调用之前 - 存储在 rsp+10 中,然后加载 0x8a(第 138 行)。如果您向我们展示了代码,那很可能表明您访问此代码是错误的。 (感谢@CodyGray 解决了关于调用约定的问题)

标签: c gcc assembly crash att


【解决方案1】:
void
tesysLog(W16 uid, char *file, int line, int level,
         W16 state, W16 event, int error, char *format, ...)

tesysLog(253, _ _FILE__, _ _LINE__, 3, 
        0, 0, result,     "error(code = %d) is %d instead of %d\n",
        header.code, decoded, size);

参数将在寄存器中:

  • rdi : W16 uid
  • rsi : 字符 *file
  • rcx : int 行
  • rdx : int 级别
  • r8 : W16 状态
  • r9:W16 事件
  • stack: char *format, ...)

你是对的,edx 是第三个参数,但是:

您必须在调用前检查 edx 是什么,它不是 0x08 ...它是 $0x8a(第 138 行),所以“_ _LINE__”不是造成麻烦的那个,而是存储在 (%rsp +10),即“header.code”

编辑:废话。 mode = 138,不是行!!

0x00000000009027e8 <+120>: mov 0x8,%edx         ; here EDX is just a tmp variable
...
0x0000000000902814 <+164>: mov %edx,0x10(%rsp)  ; for THIS value!
0x0000000000902818 <+168>: mov $0x8a,%edx       ; <-- THIS is edx on call

如果您透露代码,我们可以找到问题...我 99.9% 确定您使用“header.code”错误;-)

【讨论】:

  • 非常感谢。明天我上任时我会透露密码。顺便说一句,您的答案中提到的“第 138 行”,您真的是指第 168 行吗,因为 sn-p 中没有第 138 行。
  • 还有一件事我不明白,因为 reg edx 将被立即值 $0x8a 替换,这是我的代码中的实际行号,为什么 gcc 想要访问位置 0x8 的内存?
  • 我想我现在明白了。是的,你是对的。 "mov 0x8, %edx" 正在加载 header.code。实际上,我们与 header.code 相关的代码是 "avp->header.code"。 (我已经在我的问题中更新了这个)。并且在调用tesysLog之前,指针avp会在另一个函数中设置为NULL,并且avp中的成员头有8字节的偏移量。这就是 gcc 生成“mov 0x8, %edx”的原因。感谢您的好回答,这有助于我了解问题所在。再次感谢。
猜你喜欢
  • 1970-01-01
  • 2021-06-28
  • 1970-01-01
  • 1970-01-01
  • 2017-06-22
  • 2012-08-08
  • 1970-01-01
相关资源
最近更新 更多