【问题标题】:Assembly: C++ stack variable addresses different/wrong?汇编:C++ 堆栈变量地址不同/错误?
【发布时间】:2012-11-09 22:33:29
【问题描述】:

我不明白为什么获取一个变量的地址很好,而另一个让我得到 0xD,然后由于在无效地址(r.thefn(0); 中的 0xD)写入值而崩溃。

这是显示两个变量没有相似地址的输出。 Here is what GDB showed and the assembly output。我的 x86 程序集不是很好(我从未编写过 x86 程序集)。我不知道它是否显示了足够的信息,但如果我没有,你能告诉我调试它还需要什么吗?为什么一个变量是 0xBF8BAF1C 而另一个是 0xD?下面是 C++ 和汇编代码,但在上面的 gist 链接中格式更好。

有一个 static_assert 将 String 强制为 POD,这意味着没有非平凡的构造函数。它使用 C++ 生成的默认构造函数。它也在堆栈上,这意味着如果new 过载,它不会影响它。 & 没有重载,但在函数调用的前两次看起来也是正确的。

什么可能会影响r 地址?我可以看到变量varme 的地址在第二次和第三次调用时相同,但第三次r 却神奇地不同。

这可以使用 Visual C++(2012 年工作)、g++ 4.6.2 正确编译和运行,使用 g++ 3.7、3.6.3 和 Clang 3.0 在 Linux (Ubuntu) 上失败。

sanity check 1 0xbf8bb4cc
sanity check 2 0xbf8bb4cc 0xbf8bb538
sanity check 3 0xbf8bb4cc 0xbf8bb538
this 0xbf8bb538
sanity check 1 0xbf8baf1c
sanity check 2 0xbf8baf1c 0xbf8baf40
sanity check 3 0xbf8baf1c 0xbf8baf40
this 0xbf8baf40
sanity check 1 0xbf8baf1c
sanity check 2 0xbf8baf1c 0xd
sanity check 3 0xbf8baf1c 0xd
this 0xd

这里是代码:另一个注意事项是 String 上有一个 static_assert,它强制它是一个 POD,这意味着没有非默认构造函数。我检查了运算符& 没有重载。

static int aa=0;
aa++;
int varme;

printf("sanity check 1 %p\n", &varme);
String r;
printf("sanity check 2 %p %p\n", &varme, &r);
//auto v=anotherfn(sz);
printf("sanity check 3 %p %p\n", &varme, &r);
//printf("callingfn=%s,%d %p %p\n", sz,aa, v, &r);
r.thefn(0);
return r;

   ¦0x8084101 <callingfn(char const*)+1> mov %esp,%ebp ¦
   ¦0x8084103 <callingfn(char const*)+3> push %esi ¦
   ¦0x8084104 <callingfn(char const*)+4> sub $0x34,%esp ¦
   ¦0x8084107 <callingfn(char const*)+7> mov 0xc(%ebp),%eax ¦
   ¦0x808410a <callingfn(char const*)+10> mov 0x8(%ebp),%ecx ¦
   ¦0x808410d <callingfn(char const*)+13> mov %eax,-0x8(%ebp) ¦
   ¦0x8084110 <callingfn(char const*)+16> mov 0x81bc894,%eax ¦
   ¦0x8084115 <callingfn(char const*)+21> lea 0x1(%eax),%eax ¦
   ¦0x8084118 <callingfn(char const*)+24> mov %eax,0x81bc894 ¦
   ¦0x808411d <callingfn(char const*)+29> lea -0xc(%ebp),%eax ¦
   ¦0x8084120 <callingfn(char const*)+32> mov %esp,%edx ¦
   ¦0x8084122 <callingfn(char const*)+34> mov %eax,0x4(%edx) ¦
   ¦0x8084125 <callingfn(char const*)+37> movl $0x812ee78,(%edx) ¦
   ¦0x808412b <callingfn(char const*)+43> mov %ecx,-0x10(%ebp) ¦
   ¦0x808412e <callingfn(char const*)+46> mov %eax,-0x14(%ebp) ¦
   ¦0x8084131 <callingfn(char const*)+49> call 0x8049a90 <printf@plt> ¦
   ¦0x8084136 <callingfn(char const*)+54> mov %esp,%ecx ¦
   ¦0x8084138 <callingfn(char const*)+56> mov -0x10(%ebp),%edx ¦
   ¦0x808413b <callingfn(char const*)+59> mov %edx,0x8(%ecx) ¦
   ¦0x808413e <callingfn(char const*)+62> mov -0x14(%ebp),%esi ¦
   ¦0x8084141 <callingfn(char const*)+65> mov %esi,0x4(%ecx) ¦
   ¦0x8084144 <callingfn(char const*)+68> movl $0x812ee8b,(%ecx) ¦
   ¦0x808414a <callingfn(char const*)+74> mov %eax,-0x18(%ebp) ¦
   ¦0x808414d <callingfn(char const*)+77> call 0x8049a90 <printf@plt> ¦
   ¦0x8084152 <callingfn(char const*)+82> mov %esp,%ecx ¦
   ¦0x8084154 <callingfn(char const*)+84> mov -0x10(%ebp),%edx ¦
   ¦0x8084157 <callingfn(char const*)+87> mov %edx,0x8(%ecx) ¦
   ¦0x808415a <callingfn(char const*)+90> mov -0x14(%ebp),%esi ¦
   ¦0x808415d <callingfn(char const*)+93> mov %esi,0x4(%ecx) ¦
   ¦0x8084160 <callingfn(char const*)+96> movl $0x812eea1,(%ecx) ¦
   ¦0x8084166 <callingfn(char const*)+102> mov %eax,-0x1c(%ebp) ¦
   ¦0x8084169 <callingfn(char const*)+105> call 0x8049a90 <printf@plt> ¦
                                                                               ¦
   ¦0x8084169 <callingfn(char const*)+105> call 0x8049a90 <printf@plt> ¦
   ¦0x808416e <callingfn(char const*)+110> mov %esp,%ecx ¦
   ¦0x8084170 <callingfn(char const*)+112> mov -0x10(%ebp),%edx ¦
   ¦0x8084173 <callingfn(char const*)+115> mov %edx,(%ecx) ¦
   ¦0x8084175 <callingfn(char const*)+117> movl $0x0,0x4(%ecx) ¦
   ¦0x808417c <callingfn(char const*)+124> mov %eax,-0x20(%ebp) ¦
   ¦0x808417f <callingfn(char const*)+127> call 0x8056d00 <SomeClass<blah>::thefn(blah*)> ¦
  >¦0x8084184 <callingfn(char const*)+132> add $0x34,%esp ¦
   ¦0x8084187 <callingfn(char const*)+135> pop %esi ¦
   ¦0x8084188 <callingfn(char const*)+136> pop %ebp ¦
   ¦0x8084189 <callingfn(char const*)+137> ret $0x4 ¦
   ¦0x808418c nopl 0x0(%eax)

【问题讨论】:

  • 你能放更多代码吗?
  • 几乎可以肯定,该错误存在于您未向我们展示的代码中,可能是String::String
  • valgrind 很有可能会为您解决这个问题。否则,没有源代码是无法调试的。
  • 根据 (N)RVO,命名 var 的地址作为附加参数传递给函数。 C 代码不这样做,所以基本上从 C 调用的 C++ 函数采用堆栈顶部的任何内容,参数应该是......我认为 RVO 不适用于具有 C 链接的函数,你如果您需要与 C 代码互操作,无论如何都应该这样做。 (如果您在本次讨论开始时知道您正在使用 C 语言,那真是太好了:)

标签: c++ assembly x86 segmentation-fault


【解决方案1】:

设置:

String 定义为:

struct String {
    void *p;
    #ifdef __cplusplus
    /* Operators to help with comparing, etc. */
    /* No additional data members */
    void thefn(int arg); /* Return/argument type not relevant */
    #endif
};

并包含用于验证 sizeof(String) == sizeof(void *) 和结构的 POD 的断言。


问题中最初没有提到这部分:调用此函数的函数将相同的 String 对象返回给它的调用者,但它是从外部 C 代码调用的,调用者期望一个简单的void * 而不是String。作者的期望是这应该可以工作,因为返回值的大小和布局是相同的。


问题:

C++ 编译器在这个函数中使用了 named return value optimization (NRVO)。函数签名从

String fn(char const *);

void fn(char const *, String *);

这在反汇编中是可见的,其中ebp+0xC 在写入之前会被读取,并且无需花费任何精力将有意义的结果放入EAXret 0x4 部分有点奇怪,因为它意味着只有一个参数从堆栈中清除,但显然 GCC/Clang 选择通过让调用者清除附加参数来实现这一点。

大概在调用函数中应用了相同的优化。但是 C 编译器认为没有理由应用这种优化(毕竟,它希望结果是 void*,而不是结构)并希望像任何指针大小的结果一样传递返回值。

结果:

  1. C 代码只将一个参数传递给 C++ 代码,它需要两个,堆栈顶部的垃圾被解释为第二个参数。
  2. C++ 代码不会在 C 代码期望找到的地方产生有意义的返回值。

解决办法:

修复的第一步显然是确保 C 代码期望与 C++ 代码相同的返回值,即结构而不是指针。

但是,我认为没有办法控制是否应用 NRVO,所以我怀疑即使返回类型正确,代码的两侧仍然可以应用此优化不一致,考虑到结构的小尺寸。我也不知道extern "C" 是否会对它产生任何影响。

(此答案总结了 cmets 中所说的内容,并进行了一些猜测以填补空白)

【讨论】:

  • 因为你真的阅读了程序集,给了我一个提示,而且因为你不烂,我通过赏金给你 500rep 因为这是我可以设置的最大值
  • @acidzombie24:您确实需要使用勾选下方的按钮来奖励赏金 :)
  • @Goz:哦,哎呀哈哈。我以前从来没有这样做过。我之前做过 2 次自动接受并自动给予赏金
猜你喜欢
  • 1970-01-01
  • 2020-09-23
  • 1970-01-01
  • 1970-01-01
  • 2021-10-28
  • 2011-01-22
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
相关资源
最近更新 更多