大会:C ++堆栈变量的地址不同/错了吗? [英] Assembly: C++ stack variable addresses different/wrong?

查看:133
本文介绍了大会:C ++堆栈变量的地址不同/错了吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这是表示不具有类似地址的两个变量的输出。 下面是GDB显示和汇编输出。我的x86汇编不是很大(我从来没有写过x86汇编)。我不知道这是否显示足够的信息,但如果我不这样做,你能告诉我还有需要什么来调试呢?为什么是一个变量0xBF8BAF1C,另一个是为0xD? C ++和汇编code是低于但在上面的链接要点格式化好。

有一个 static_assert 执行字符串是一个 POD 的,这意味着没有非平凡的构造函数。它使用的是默认的构造函数C ++生成。这也是堆栈,这意味着,如果超载都不会影响它。 &放大器; 不超载,但它也看起来是正确的前两次调用函数

什么可能影响研究地址?我可以看到变量 varme 的地址是相同的,它被称为第二次和第三次,但第三次研究神奇的是不同的。

这编译和运行正确使用Visual C ++(2012年作品),G ++ 4.6.2,在失败 Linux的使用( Ubuntu的)G ++ 3.7,3.6.3和的的3.0。

 完整性检查1 0xbf8bb4cc
健全检查2 0xbf8bb4cc 0xbf8bb538
健全检查3 0xbf8bb4cc 0xbf8bb538
这0xbf8bb538
健全检查1 0xbf8baf1c
健全检查2 0xbf8baf1c 0xbf8baf40
健全检查3 0xbf8baf1c 0xbf8baf40
这0xbf8baf40
健全检查1 0xbf8baf1c
健全检查2 0xbf8baf1c 0xd中
健全检查3 0xbf8baf1c 0xd中
这0xd中

下面是code:一个额外的说明是有一个 static_assert 的字符串,它强制执行,这是一个POD,这意味着没有非默认构造函数。我检查了经营者&放大器; 不超载

 静态INT AA = 0;
AA +;
INT varme;的printf(健康检查1%P \\ N,&安培; varme);
串R;
的printf(健康检查2%P%P \\ N,&安培; varme,&安培; R);
//自动V = anotherfn(SZ);
的printf(健康检查3%P%P \\ N,&安培; varme,&安培; R);
//输出(callingfn =%s的%D%P%P \\ N,SZ,AA,V&安培; R);
r.thefn(0);
返回ř;   |0x8084101< callingfn(字符常量*)+ 1 GT; MOV%ESP,EBP%|
   |0x8084103&所述; callingfn(字符常量*)+3;推%ESI |
   |0x8084104&所述; callingfn(字符常量*)+ 4为H.子$ 0x34,%ESP |
   |0x8084107&所述; callingfn(字符常量*)+ 7是H. MOV位于0xC(EBP%),%EAX |
   |0x808410a< callingfn(字符常量*)+ 10 -10 MOV,位于0x8(%EBP),ECX%|
   |0x808410d< callingfn(字符常量*)+ 13取代; MOV EAX%,-0x8(EBP%)|
   |0x8084110< callingfn(字符常量*)+ 16取代; MOV 0x81bc894,%eax中|
   |0x8084115< callingfn(字符常量*)+ 21基LEA为0x1(%eax中),%eax中|
   |0x8084118< callingfn(字符常量*)+ 24 GT; MOV EAX%,0x81bc894 |
   |0x808411d< callingfn(字符常量*)+ 29 GT; LEA -0xc(EBP%),%EAX |
   |0x8084120< callingfn(字符常量*)+ 32取代; MOV%ESP,EDX%|
   |0x8084122< callingfn(字符常量*)+ 34 GT; MOV EAX%,为0x4(%EDX)|
   |0x8084125< callingfn(字符常量*)+ 37 GT; MOVL $ 0x812ee78(%EDX)|
   |0x808412b< callingfn(字符常量*)+ 43 GT; MOV%ECX,-0x10(EBP%)|
   |0x808412e< callingfn(字符常量*)+ 46 GT; MOV EAX%,-0x14(EBP%)|
   |0x8084131< callingfn(字符常量*)+ 49 GT;调用0x8049a90< printf的PLT @> |
   |0x8084136< callingfn(字符常量*)+ 54 GT; MOV%ESP,ECX%|
   |0x8084138< callingfn(字符常量*)+ 56 GT; MOV -0x10(EBP%),EDX%|
   |0x808413b< callingfn(字符常量*)+ 59 GT; MOV%EDX,地址0x8(ECX%)|
   |0x808413e< callingfn(字符常量*)+ 62 GT; MOV -0x14(EBP%),ESI%|
   |0x8084141< callingfn(字符常量*)+ 65 GT; MOV%ESI,为0x4(ECX%)|
   |0x8084144< callingfn(字符常量*)+ 68 MOVL $ 0x812ee8b,(ECX%)|
   |0x808414a< callingfn(字符常量*)+ 74 GT; MOV EAX%,-0x18(EBP%)|
   |0x808414d< callingfn(字符常量*)+ 77 GT;调用0x8049a90< printf的PLT @> |
   |0x8084152< callingfn(字符常量*)+ 82 GT; MOV%ESP,ECX%|
   |0x8084154< callingfn(字符常量*)+ 84 GT; MOV -0x10(EBP%),EDX%|
   |0x8084157< callingfn(字符常量*)+ 87 GT; MOV%EDX,地址0x8(ECX%)|
   |0x808415a< callingfn(字符常量*)+ 90 GT; MOV -0x14(EBP%),ESI%|
   |0x808415d< callingfn(字符常量*)+ 93基%MOV ESI,为0x4(ECX%)|
   |0x8084160< callingfn(字符常量*)+ 96 GT; MOVL $ 0x812eea1,(ECX%)|
   |0x8084166< callingfn(字符常量*)+ 102 GT; MOV EAX%,-0x1c(EBP%)|
   |0x8084169< callingfn(字符常量*)+ 105 GT;调用0x8049a90< printf的PLT @> |
                                                                               |
   |0x8084169< callingfn(字符常量*)+ 105 GT;调用0x8049a90< printf的PLT @> |
   |0x808416e< callingfn(字符常量*)+ 110 GT; MOV%ESP,ECX%|
   |0x8084170< callingfn(字符常量*)+ 112 GT; MOV -0x10(EBP%),EDX%|
   |0x8084173< callingfn(字符常量*)+ 115 GT; MOV%EDX,(ECX%)|
   |0x8084175< callingfn(字符常量*)+ 117 GT; MOVL $ 0x0,0x4(ECX%)|
   |0x808417c< callingfn(字符常量*)+ 124 GT; MOV EAX%,-0x20(EBP%)|
   |0x808417f< callingfn(字符常量*)+ 127 GT;调用0x8056d00< SomeClass的<等等> :: thefn(废话*)> |
  > |0x8084184< callingfn(字符常量*)+ 132 GT;加$ 0x34,%ESP |
   |0x8084187< callingfn(字符常量*)+ 135 GT;弹出%ESI |
   |0x8084188< callingfn(字符常量*)+ 136 GT;流行的%ebp |
   |0x8084189< callingfn(字符常量*)+ 137 GT; RET $为0x4 |
   |0x808418c nopl为0x0(%eax中)


解决方案

的设置:

字符串的定义是:

 结构字符串{
    无效* P;
    #IFDEF __cplusplus
    / *运营商,以帮助比较等。* /
    / *无需额外的数据成员* /
    无效thefn(INT ARG); / *回报/参数类型无关* /
    #万一
};

和包括断言验证的sizeof(字符串)==的sizeof(无效*)和结构的POD的烦躁。


这部分最初未在问题的提到:调用该函数的函数返回相同的字符串对象到其调用者,但它的从外部C code,其中主叫预计称为一个简单的无效* 而不是字符串。笔者的预期是,这应该工作,因为返回值的大小和布局是一样的。


问题:

C ++编译器所使用的 命名返回值优化 的(NRVO)在此功能。函数签名更改从

 字符串FN(字符常量*);

 无效FN(字符常量*,字符串*);

这是可见的反汇编,其中 EBP + 0xC的从写之前阅读,而且也没有力气花在把有意义的成果转化的 EAX 。在 RET为0x4 部分是有些奇怪,因为这意味着只有一个参数被清除出栈,但显然这是GCC / Clang的选择如何实现这一点,通过使来电者清关的额外的参数。

presumably,相同的优化是在调用函数的应用。但是,C编译器认为没有理由要应用此优化(毕竟,预计结果将在无效* ,而不是一个结构)和预期的返回值传递像任何指针大小的结果将是。

结果:


  1. 的C code会只有一个参数为C ++ code,它需要两个,而在堆栈顶部的垃圾得到PTED作为第二个参数间$ P $。

  2. C ++的code不会产生其中C code期望找到一个有意义的返回值。


解决方案:

迈向修复明显的第一步是确保C code期望的同样的返回值作为C ++ code,一个结构,而不是一个指针。

然而的,我不认为有一种方法来控制NRVO是否应用了,所以我怀疑即使有正确的返回类型,它仍然有可能为code的双方不一致将此优化,给定结构的尺寸小。我不知道,如果的externC会有什么影响。

(这个答案总结什么在评论中说,一些猜测,填补国内空白)

I don't understand why getting the address of one variable is fine and the other gets me 0xD which then crashes due to writing a value at an invalid address (0xD in r.thefn(0);).

This is the output which shows the two variables not having a similar address. Here is what GDB showed and the assembly output. My x86 assembly isn't great (I have never written x86 assembly). I don't know if it shows enough information, but if I don't, can you tell me what else is required to debug this? Why is one variable 0xBF8BAF1C and the other is 0xD? C++ and assembly code is below but formatted better in the gist link above.

There is a static_assert enforcing String to be a POD which means no nontrival constructors. It is using the default constructor C++ generates. It's also on the stack which means if new is overloaded it wouldn't affect it. & isn't overloaded, but it also looks correct the first two times the function is called.

What might affect the r address? I can see variable varme's address is the same the second and third time it's called, but the third time r is magically different.

This compiles and runs properly using Visual C++ (2012 works), g++ 4.6.2, fails on Linux (Ubuntu) using g++ 3.7, 3.6.3 and Clang 3.0.

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

Here is the code: One additional note is there is a static_assert on String which enforces that it is a POD which means no non-default constructor. I checked operator & isn't overloaded.

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)

解决方案

The setup:

String is defined as:

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
};

and includes asserts to verify sizeof(String) == sizeof(void *) and the struct's POD-ness.


This part was not originally mentioned in the question: the function that calls this function returns the same String object to its caller, but it's called from external C code, where the caller expects a simple void * instead of String. The author's expectation was that this should work, because the return value's size and layout is the same.


The problem:

The C++ compiler used named return value optimization (NRVO) in this function. The function signature changed from

String fn(char const *);

to

void fn(char const *, String *);

This is visible in the disassembly, where ebp+0xC is read from before writing to it, and there's no effort spent on putting meaningful results into EAX. The ret 0x4 part was a little strange, since it implies only one argument is cleared off the stack, but apparently that is how GCC/Clang choose to implement this, by having the caller clear off the additional argument.

Presumably, the same optimization was applied in the caller function. But the C compiler saw no reason to apply this optimization (after all, it expected the result to be a void*, not a structure) and expected the return value to be passed like any pointer-sized result would be.

As a result:

  1. the C code passes only one argument into C++ code, which expects two, and the garbage at the top of the stack gets interpreted as the second argument.
  2. The C++ code doesn't produce a meaningful return value where C code expects to find one.


The solution:

The obvious first step towards a fix is to make sure the C code expects the same return value as the C++ code, a struct instead of a pointer.

However, I don't think there's a way to control whether NRVO is applied, so I suspect even with the correct return type it's still possible for the two sides of the code to apply this optimization inconsistently, given the small size of the structure. I also have no idea if extern "C" would have any effect on it.

(This answer summarizes what was said in the comments, with some guesswork to fill the gaps)

这篇关于大会:C ++堆栈变量的地址不同/错了吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆