此汇编函数调用安全/完整吗? [英] Is this assembly function call safe/complete?

查看:101
本文介绍了此汇编函数调用安全/完整吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我没有组装经验,但这是我一直在努力的工作.如果我缺少传递参数和在汇编中通过指针调用函数的任何基本方面,我都希望输入.

I don't have experience in assembly, but this is what I've been working on. I would like input if I'm missing any fundamental aspects to passing parameters and calling a function via pointer in assembly.

例如,我想知道是否应该恢复ecxedxesiedi.我读到它们是通用寄存器,但是我找不到它们是否需要还原?通话后我应该进行任何清理吗?

For instance I'm wondering if I supposed to restore ecx, edx, esi, edi. I read they are general purpose registers, but I couldn't find if they need to be restored? Is there any kind of cleanup I am supposed to do after a call?

这是我现在拥有的代码,它确实有效:

This is the code I have now, and it does work:

#include "stdio.h"

void foo(int a, int b, int c, int d)
{
  printf("values = %d and %d and %d and %d\r\n", a, b, c, d);
}

int main()
{

  int a=3,b=6,c=9,d=12;
  __asm__(
          "mov %3, %%ecx;"
          "mov %2, %%edx;"
          "mov %1, %%esi;"
          "mov %0, %%edi;"
          "call %4;"
          :
          : "g"(a), "g"(b), "g"(c), "g"(d), "a"(foo)
          );

}

推荐答案

最初的问题是Is this assembly function call safe/complete?.答案是:不会.尽管在这个简单的示例中它似乎可以工作(尤其是禁用优化功能),但您违反的规则最终会导致失败(真的很难追查).

The original question was Is this assembly function call safe/complete?. The answer to that is: no. While it may appear to work in this simple example (especially if optimizations are disabled), you are violating rules that will eventually lead to failures (ones that are really hard to track down).

我想解决有关如何使其安全的(显而易见的)后续问题,但是如果没有OP的实际意图反馈,我将无法做到这一点.

I'd like to address the (obvious) followup question of how to make it safe, but without feedback from the OP on the actual intent, I can't really do that.

因此,我将竭尽所能,并尝试描述使它不安全的事情以及您可以对此做的一些事情.

So, I'll do the best I can with what we have and try to describe the things that make it unsafe and some of the things you can do about it.

让我们先简化asm:

 __asm__(
          "mov %0, %%edi;"
          :
          : "g"(a)
          );

即使只有这一条语句,该代码也已经是不安全的.为什么?因为我们在不通知编译器的情况下更改了寄存器(edi)的值.

Even with this single statement, this code is already unsafe. Why? Because we are changing the value of a register (edi) without letting the compiler know.

编译器如何不知道您的要求?毕竟,它就在asm中!答案来自 gcc文档中的这一行:

How can the compiler not know you ask? After all, it's right there in the asm! The answer comes from this line in the gcc docs:

GCC不会自行解析汇编程序指令,并且不会 知道它们的含义,甚至知道它们是否是有效的汇编程序输入.

GCC does not parse the assembler instructions themselves and does not know what they mean or even whether they are valid assembler input.

在这种情况下,您如何让gcc知道发生了什么事?答案在于使用约束条件(冒号后面的内容)来描述asm的影响.

In that case, how do you let gcc know what's going on? The answer lies in using the constraints (the stuff after the colons) to describe the impact of the asm.

也许修复此代码的最简单方法如下:

Perhaps the simplest way to fix this code would be like this:

  __asm__(
          "mov %0, %%edi;"
          :
          : "g"(a)
          : edi
          );

这会将edi添加到主题列表中.简而言之,这告诉gcc edi的值将被代码更改,并且当asm退出时gcc不应假定其中会有任何特定值.

This adds edi to the clobber list. In brief, this tell gcc that the value of edi is going to be changed by the code, and that gcc shouldn't assume any particular value will be in it when the asm exits.

现在,虽然这是最简单的方法,但不一定是最好的方法.考虑以下代码:

Now, while that's the easiest, it's not necessarily the best way. Consider this code:

  __asm__(
          ""
          :
          : "D"(a)
          );

这使用机器约束告诉gcc输入值变量a进入edi寄存器.这样,gcc会在方便"的时候为您加载寄存器,也许总是将a保留在edi中.

This uses a machine constraint to tell gcc to put the value of the variable a into the edi register for you. Doing it this way, gcc will load the register for you at a 'convenient' time, perhaps by always keeping a in edi.

此代码有一个(重要的)警告:通过将参数放在第二个冒号之后,我们将其声明为输入.输入参数必须是只读的(即,退出asm时它们必须具有相同的值).

There is one (significant) caveat to this code: By putting the parameter after the 2nd colon, we are declaring it to be an input. Input parameters are required to be read-only (ie they must have the same value on exiting the asm).

在您的情况下,call语句意味着我们将无法保证不会更改edi,因此这行不通.有几种方法可以解决这个问题.最简单的方法是将约束向上移动到第一个冒号之后,使其成为输出,并指定"+D"表示该值是可读写的.但是然后a的内容在asm之后几乎是未定义的(printf可以将其设置为任何内容).如果销毁a是不可接受的,则总会有这样的情况:

In your case, the call statement means that we won't be able to guarantee that edi won't be changed, so this doesn't quite work. There are a few ways to deal with this. The easiest is to move the constraint up after the first colon, making it an output, and specify "+D" to indicate that the value is read+write. But then the contents of a are going to be pretty much undefined after the asm (printf could set it to anything). If destroying a is unacceptable, there's always something like this:

int junk;
  __asm__ volatile (
          ""
          : "=D" (junk)
          : "0"(a)
          );

这告诉gcc,启动asm时,应将变量a的值放在与输出约束#0(即edi)相同的位置.它还说,在输出时,edi不再是a,它将包含变量junk.

This tells gcc that on starting the asm, it should put the value of the variable a into the same place as output constraint #0 (ie edi). It also says that on output, edi won't be a anymore, it will contain the variable junk.

编辑:由于实际上不会使用垃圾"变量,因此我们需要添加volatile限定词.没有任何输出参数时,Volatile是隐式的.

Since the 'junk' variable isn't actually going to be used, we need to add the volatile qualifier. Volatile was implicit when there weren't any output parameters.

该行的另一点:您以分号结尾.这是合法的,并且会按预期工作.但是,如果您曾经想使用-S命令行选项来确切地查看生成了什么代码(并且如果想对嵌入式asm有所了解,那么您会发现),那么您会发现这会产生难以阅读的代码.我建议使用\n\t代替分号.

One other point on that line: You end it with a semi-colon. This is legal and will work as expected. However, if you ever want to use the -S command line option to see exactly what code got generated (and if you want to get good with inline asm, you will), you will find that produces difficult-to-read code. I'd recommend using \n\t instead of a semi-colon.

所有这些我们仍然在第一行...

All that and we're still on the first line...

显然,其他两个mov语句也是如此.

Obviously the same would apply to the other two mov statements.

这使我们进入了call语句.

Michael和我都列出了许多难以通过内联asm进行呼叫的原因.

Both Michael and I have listed a number of reasons doing call in inline asm is difficult.

  • 处理所有可能被函数调用的ABI破坏的寄存器.
  • 处理红色区域.
  • 处理对齐方式.
  • 内存破坏者.

如果此处的目标是学习",请随时尝试.但是我不知道在生产代码中这样做会不会很舒服.即使它看起来可行,我也永远不会有信心我不会错过任何奇怪的案例.除了我通常对完全使用嵌入式asm 的担心.

If the goal here is 'learning,' then feel free to experiment. But I don't know that I would ever feel comfortable doing this in production code. Even when it looks like it works, I'd never feel confident there wasn't some weird case I'd missed. That's aside from my normal concerns about using inline asm at all.

我知道,这是很多信息.作为gcc的asm命令的简介,它可能比您想要的要多,但您选择了一个具有挑战性的起点.

I know, that's a lot of information. Probably more than you were looking for as an introduction to gcc's asm command, but you've picked a challenging place to start.

如果您还没有这样做,请花一些时间查看gcc

If you haven't done so already, spend time looking over all the docs in gcc's Assembly Language interface. There's a lot of good information there along with examples to try to explain how it all works.

这篇关于此汇编函数调用安全/完整吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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