程序运行周期中,代码块Ver.16.01发生崩溃 [英] Code blocks Ver.16.01 crashing during run cycle of programme

查看:168
本文介绍了程序运行周期中,代码块Ver.16.01发生崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个程序已经被证明可以运行在较旧版本的代码块(版本13.12)上,但在新版本(版本16.01)上试用时似乎不起作用。该程序的目的是输入两个整数,然后将被添加,等等。它使用我新的asm代码。我的问题是为什么在我输入2个整数后按下回车键后窗户停止了响应?



以下是代码:

  //程序16 

#include< stdio.h>
#include< iostream>
使用namespace std;

int main(){

int arg1,arg2,add,sub,mul,quo,rem;

cout<< 输入两个整数:;
cin>> arg1>> arg2;
cout<< ENDL;
$ b $ asm(addl %% ebx,%% eax;:= a(add):a(arg1),b(arg2));
asm(subl %% ebx,%% eax;:= a(sub):a(arg1),b(arg2));
asm(imull %% ebx,%% eax;:= a(mul):a(arg1),b(arg2));

asm(movl $ 0x0,%% edx;
movl%2,%% eax;
movl%3,%% ebx;
idivl %% ebx;:= a(quo),= d(rem):g(arg1),g(arg2));

cout<< arg1<< +<< arg2<< =<<添加<< ENDL;
cout<< arg1<< - << arg2<< =<< sub<< ENDL;
cout<< arg1<< x<< arg2<< =<< mul< ENDL;
cout<< arg1<< /<< arg2<< =<<现有技术<< ;
cout<< 余数<< rem<< ENDL;

返回0;
}


解决方案

David Wohlferd给出了非常关于如何更好地使用 GCC 扩展程序集模板来完成现有代码的工作,这是一个很好的答案。



可能会出现一个问题,代码显示失败,代码块16.01 w / GCC ,因为它可能以前工作过。因为它的代码看起来很简单,所以可能会出现什么问题?



我推荐的最好的事情是学习使用调试器并在代码块中设置断点。这很简单(但超出了这个答案的范围)。您可以在 Codeblocks文档中了解有关调试的更多信息。



如果您使用带有Codeblocks 16.01的调试器以及库存的C ++控制台项目,您可能已经发现该程序正在为您提供一个 Arithmetic Exception 在装配模板中的 IDIV 指令上。这是出现在我的控制台输出:


程序接收到的信号SIGFPE,算术例外。







这些代码行如你所料:

  asm(addl %% ebx,%% eax;:= a(add):a(arg1),b(arg2)); 
asm(subl %% ebx,%% eax;:= a(sub):a(arg1),b(arg2));
asm(imull %% ebx,%% eax;:= a(mul):a(arg1),b(arg2));

这是有问题的地方:

  asm(movl $ 0x0,%% edx;
movl%2,%% eax;
movl%3,%% ebx;
idivl %% ebx;:= a(quo),= d(rem):g(arg1),g(arg2));

Codeblocks可以为您做的一件事是向您展示它生成的汇编代码。拉下 Debug 菜单,选择 Debugging Windows> 反汇编手表 CPU寄存器窗口我强烈推荐。



如果您使用CodeBlocks 16.01 w / GCC查看生成的代码,您可能会发现它产生了这样的结果:

  / *自动生成由输入约束的装配模板* / 
mov -0x20(%ebp),%eax / * EAX = arg1的值* /
mov -0x24(%ebp),%edx / * EDX = arg2的值* /

/ *我们的汇编模板指令* /
mov $ 0x0,%edx / * EDX = 0 - 我们只是修改了以前的EDX! * /
mov%eax,%eax / * EAX保持不变* /
mov%edx,%ebx / * EBX = EDX = 0 * /
idiv%ebx / * EBX是0,所以这是除零! *

/ *由输出约束的装配模板自动生成* /
mov%eax,-0x18(%ebp)/ *现值= EAX * /
mov %edx,-0x1c(%ebp)/ *物业价值= EDX * /

我已评论代码,它应该是显而易见的为什么这个代码将无法正常工作。我们有效地结束了将零置入 EBX ,然后试图用它作为 IDIV 的除数,并产生一个算术异常(在这种情况下除以零)。 p>

发生这种情况的原因是,在输出操作数被写入之前,(默认情况下)GCC 会假定所有输入操作数都被使用(消耗)。我们从未告诉GCC 它不可能使用与输出操作数相同的输入操作数。 GCC 认为这种情况是 早期Clobber 。它提供了一种机制,可以使用& (&符号)修饰符将输出约束标记为早期clobber:


`&'

意味着(在一个特定的替代方案中)这个操作数是一个earlyclobber操作数,它在指令完成前使用输入操作数。因此,该操作数可能不在用作输入操作数或任何内存地址的寄存器中。


通过更改操作数以便处理早期的clobbers,我们可以在这两个输出约束中放置&

 idivl %% ebx; :=& a(quo),=& d(rem):g(arg1),g(arg2)); 

在这种情况下 arg1 arg2 不会通过任何标有& 的操作数传入。这意味着这段代码将避免对输入操作数 arg1 arg2使用 EAX EDX



另一个问题是您的代码修改了 EBX ,但您不告诉 GCC 。您可以简单地将 EBX 添加到程序集模板中的clobber列表中,如下所示:

 idivl% %EBX; :=& a(quo),=& d(rem):g(arg1),g(arg2):ebx); 

所以这段代码应该可以工作,但效率不高:

  asm(movl $ 0x0,%% edx;
movl%2,%% eax;
movl%3,% %ebx;
idivl %% ebx;:=& a(quo),=& d(rem):g(arg1),g(arg2) EBX);

生成的代码现在看起来像这样:

  / *由输入约束的汇编模板自动生成* / 
mov -0x30(%ebp),%ecx / * ECX = arg1的值* /
mov -0x34(%ebp),%esi / * ESI = arg2的值* /

/ *我们的汇编模板指令* /
mov $ 0x0,%edx / * EDX = 0 * /
mov%ecx,%eax / * EAX = ECX = arg1 * /
mov%esi,%ebx / * EBX = ESI = arg2 * /
idiv%ebx
$ b $ *由汇编模板自动生成输出约束* /
mov%eax,-0x28(%ebp)/ *现值= EAX * /
mov%edx ,-0x2c(%ebp)/ * rem的值= EDX * /

这次输入操作数对于 arg1 arg2 不共享与 MOV 内联汇编模板中的指令。






为什么其他(包括旧版本)GCC工作?



如果 GCC arg1 和<$ c使用除 EAX EDX EBX $ c> arg2 操作数,那么它会起作用。但它可能发挥作用的事实只是运气。要查看随旧代码块和随附的 GCC 发生的情况,我建议您按照上面讨论的方式查看在该环境中生成的代码。



早期的clobbering和一般的注册clobbering是扩展汇编模板变得棘手的原因,扩展汇编模板的原因应该谨慎使用,特别是如果您没有充分的理解。



您可以创建似乎可以正常工作但代码不正确的代码。不同版本的 GCC 甚至不同的优化级别可能会改变代码的行为。有时候,这些错误可能非常微妙,以至于随着程序的增长,错误以其他可能难以追踪的方式显现出来。

另一个经验法则是,并非所有的代码您在互联网上发现无缺陷,扩展内联汇编的微妙复杂性在教程中经常被忽略。我发现您使用的代码似乎基于此代码项目。不幸的是,作者对所涉及的intracies没有透彻的理解。代码可能在当时有效,但现在不一定。


I have a program which has been proved to run on an older version of codeblocks (ver 13.12) but does not seem to work when I try it on the newer version (ver 16.01). The purpose of the programme is to enter two integers which will then be added, mult etc. It uses asm code which I am new at. My question is why does it say windows has stopped responding after I type 2 integers and press enter?

Here is the code:

//Program 16

#include <stdio.h>
 #include <iostream>
 using namespace std;

int main() {

int arg1, arg2, add, sub, mul, quo, rem ;

cout << "Enter two integer numbers : " ;
cin >>  arg1 >> arg2 ;
cout << endl;

  asm ( "addl %%ebx, %%eax;" : "=a" (add) : "a" (arg1) , "b" (arg2) );
  asm ( "subl %%ebx, %%eax;" : "=a" (sub) : "a" (arg1) , "b" (arg2) );
 asm ( "imull %%ebx, %%eax;" : "=a" (mul) : "a" (arg1) , "b" (arg2) );

asm ( "movl $0x0, %%edx;"
"movl %2, %%eax;"
"movl %3, %%ebx;"
"idivl %%ebx;" : "=a" (quo), "=d" (rem) : "g" (arg1), "g" (arg2) );

cout<< arg1 << "+" << arg2 << " = " << add << endl;
 cout<< arg1 << "-" << arg2 << " = " << sub << endl;
cout<< arg1 << "x" << arg2 << " = " << mul << endl;
cout<< arg1 << "/" << arg2 << " = " << quo << "  ";
 cout<< "remainder " << rem << endl;

return 0;
}

解决方案

David Wohlferd has given a very good answer on how to better work with GCC extended assembly templates to do the work of your existing code.

A question may arise as to why the code presented fails with Codeblocks 16.01 w/GCC where as it may have worked previously. As it stands the code looks pretty simple, so what could have possibly gone wrong?

The best thing I recommend is learning to use the debugger and set break points in Codeblocks. It is very simple (but beyond the scope of this answer). You can learn more about debugging in the Codeblocks documentation.

If you used the debugger with Codeblocks 16.01, with a stock C++ console project you may have discovered that the program is giving you an Arithmetic Exception on the IDIV instruction in the assembly template. This is what appears in my console output:

Program received signal SIGFPE, Arithmetic exception.


These lines of code do as you would expect:

asm ( "addl %%ebx, %%eax;" : "=a" (add) : "a" (arg1) , "b" (arg2) );
asm ( "subl %%ebx, %%eax;" : "=a" (sub) : "a" (arg1) , "b" (arg2) );
asm ( "imull %%ebx, %%eax;" : "=a" (mul) : "a" (arg1) , "b" (arg2) );

This is where was have issues:

asm ( "movl $0x0, %%edx;"
      "movl %2, %%eax;"
      "movl %3, %%ebx;"
      "idivl %%ebx;" : "=a" (quo), "=d" (rem) : "g" (arg1), "g" (arg2) );

One thing Codeblocks can do for you is show you the assembly code it generated. Pull down the Debug menu, select Debugging Windows > and Disassembly. The Watches and CPU Registers windows I highly recommend as well.

If you review the generated code with CodeBlocks 16.01 w/GCC you might discover it produced this:

/* Automatically produced by the assembly template for input constraints */
mov    -0x20(%ebp),%eax      /* EAX = value of arg1 */
mov    -0x24(%ebp),%edx      /* EDX = value of arg2 */

/* Our assembly template instructions */
mov    $0x0,%edx             /* EDX = 0 - we just clobbered the previous EDX! */
mov    %eax,%eax             /* EAX remains the same */
mov    %edx,%ebx             /* EBX = EDX = 0. */
idiv   %ebx                  /* EBX is 0 so this is division by zero!! *

/* Automatically produced by the assembly template for output constraints */
mov    %eax,-0x18(%ebp)      /* Value at quo = EAX */
mov    %edx,-0x1c(%ebp)      /* Value at rem = EDX */

I have commented the code and it should be obvious why this code won't work. We effectively ended up placing zero in EBX and then attempted to use that as a divisor with IDIV and that produced an arithmetic exception (division by zero in this case).

This happened because GCC will (by default) assume that all the input operands are used (consumed) BEFORE the output operands are written to. We never told GCC that it couldn't potentially use the same input operands as output operands. GCC considers this situation an Early Clobber. It provides a mechanism to mark an output constraint as early clobber using & (ampersand) modifier:

`&'

Means (in a particular alternative) that this operand is an earlyclobber operand, which is modified before the instruction is finished using the input operands. Therefore, this operand may not lie in a register that is used as an input operand or as part of any memory address.

By changing the operands so that the early clobbers are dealt with, we can place & on both the output constraints like this:

"idivl %%ebx;" : "=&a" (quo), "=&d" (rem) : "g" (arg1), "g" (arg2) );

In this case arg1 and arg2 will not be passed in through any of the operands marked with &. This means this code will avoid using EAX and EDX for the input operands arg1 and arg2.

The other issue is that EBX is modified by your code but you don't tell GCC. You could simply add EBX to the clobber list in the assembly template like this:

"idivl %%ebx;" : "=&a" (quo), "=&d" (rem) : "g" (arg1), "g" (arg2) : "ebx");

So this code should work, but is not efficient:

asm ( "movl $0x0, %%edx;"
      "movl %2, %%eax;"
      "movl %3, %%ebx;"
      "idivl %%ebx;" : "=&a" (quo), "=&d" (rem) : "g" (arg1), "g" (arg2) : "ebx");

The generated code will now look something like:

/* Automatically produced by the assembler template for input constraints */
mov    -0x30(%ebp),%ecx      /* ECX = value of arg1 */
mov    -0x34(%ebp),%esi      /* ESI = value of arg2 */

/* Our assembly template instructions */
mov    $0x0,%edx             /* EDX = 0 */
mov    %ecx,%eax             /* EAX = ECX = arg1 */
mov    %esi,%ebx             /* EBX = ESI = arg2 */
idiv   %ebx

/* Automatically produced by the assembler template for output constraints */
mov    %eax,-0x28(%ebp)      /* Value at quo = EAX */
mov    %edx,-0x2c(%ebp)      /* Value at rem = EDX */

This time the input operands for arg1 and arg2 didn't share the same registers that would conflict with the MOV instructions inside our inline assembly template.


Why other (including older) versions of GCC work?

If GCC had generated instructions using registers other than EAX, EDX, and EBX for arg1 and arg2 operands then it would have worked. But the fact it may have worked was just by luck. To see what happend with older Codeblocks and the GCC that came with it, I'd recommend reviewing the code generated in that environment the same way I have discussed above.

Early clobbering, and register clobbering in general is a reason that extended assembler templates can be tricky, and a reason extended assembler templates should be used sparingly especially if you don't have a solid understanding.

You can create code that appears to work, but is coded incorrectly. A different version of GCC or even different optimization levels may alter the behaviour of the code. Sometimes these bugs can be so subtle that as a program grows the bug manifests itself in other ways that may be hard to trace.

Another rule of thumb is that not all code you find on the internet is bug free, and the subtle complexities of extended inline assembly is often overlooked in tutorials. I discovered the code you used seems to be based on this Code Project. Unfortunately the author didn't have a thorough understanding of the intracies involved. The code may have worked at the time, but not necessarily now.

这篇关于程序运行周期中,代码块Ver.16.01发生崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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