【问题标题】:Calling assembly routines from C source code从 C 源代码调用汇编例程
【发布时间】:2013-04-16 19:06:10
【问题描述】:

我有这个简单的 C 源代码:

#include <stdio.h>

extern int Sum(int,int);

int main()
{
  int a,b,s;
  a=1 , b=2;
  s = Sum(a,b);
  return 0;
}

我有这个定义函数 _Sum 的 s.asm:

global _Sum

     _Sum:

        push    ebp             ; create stack frame
        mov     ebp, esp
        mov     eax, [ebp+8]    ; grab the first argument
        mov     ecx, [ebp+12]   ; grab the second argument
        add     eax, ecx        ; sum the arguments
        pop     ebp             ; restore the base pointer
        ret

现在,我使用 .asm 编译了:

nasm s.asm -f elf -o s.o

并使用 :

编译和链接 .c 文件
gcc s.o test.o -o testapp

这是结果:

/tmp/ccpwYHDQ.o: In function `main':
test.c:(.text+0x29): undefined reference to `Sum'
collect2: ld returned 1 exit status

那么问题出在哪里?

我正在使用 Ubuntu-Linux

任何帮助将不胜感激,谢谢

[已解决]:我用 nm 检查了 test.o 文件,它预计会找到符号“Sum”而不是“_Sum”,因此更改解决了问题。

【问题讨论】:

  • 就在我的脑海中,您的 sum 原型不正确。 extern int Sum(int,int);
  • 您可能需要在 asm 端的某处放置 global _Sum
  • 我试过global _Sum 而不是_Sum,我得到了同样的错误信息@harold
  • 我还将原型更改为extern int Sum(int,int); 而不是旧的,我收到了相同的错误消息@RageD
  • gcc 的“-o”选项不是用于指定输出文件吗?在这种情况下,test.c 的编译可能会覆盖 s.o 文件?

标签: c linux assembly nasm linker-errors


【解决方案1】:

在典型的汇编器中,标签默认是本地的。要告诉汇编器使它们对外部例程可见,您必须添加一个声明,例如:

.globl _Sum

此外,在 C 中正确声明例程。这不是链接错误的原因,但可能会导致其他问题:

extern int Sum(int, int);

为了完整起见,感谢评论者:不要覆盖您的目标文件。您可以组装、编译和链接:

nasm s.asm -f elf -o s.o
gcc test.c s.o -o test

(这会将可执行文件命名为“test”,您可能必须使用“./test”来执行它,以区分目录中的“test”和“test”命令。选择另一个名称可能会更开心。 )

出于教育目的:如果您的系统上有nm 工具,请执行命令nm s.o。它可能会显示如下内容:

00000000 t _Sum

t 表示_Sum 是代码部分中的本地标签。 (代码部分也称为文本部分,因此称为 t。)添加 .globl 声明并组装新源代码后,nm s.o 应该显示一个大写的 T。大写表示标签在外部可见。

【讨论】:

  • @hshihab:语法取决于您的汇编程序。我会尝试“.globl”和“.global”,然后参考文档。
  • @hshihab:系统中一般不会安装这样的程序。它们通常通过将路径作为命令名称来执行。如果当前目录包含可执行文件,则可以使用“./name”作为命令。
  • 太棒了! .. 我刚刚尝试了 nm ./s.o,它产生了00000000 T _Sum。所以现在我可以确定我的函数被声明为全局的。为什么我仍然收到错误消息?
  • @hshihab:你有没有修改“gcc”命令行以避免覆盖s.o?
  • 我在帖子中编辑了我的命令行和 .asm 文件以反映新的变化。
【解决方案2】:

据我从您的问题中可以看出,您用 C 程序覆盖了来自汇编程序 s.o 的目标文件。所以你不再有汇编程序了。

你应该写

生成s.o 目标文件

 nasm s.asm -f elf -o s.o

生成test.o(您的命令创建了另一个s.o)

 gcc test.c -c 

链接应用

 gcc s.o test.o -o testapp 

(我选择 testapp 作为输出二进制文件,因为test 对于程序来说通常是一个非常糟糕的名称,它与 Unix 命令test 冲突)

【讨论】:

  • 好的,我做了上面提到的两个更改(原型更改和全局声明)现在我在 .asm 文件中的前两行就像这样 global _Sum _Sum: 我编译并链接为你建议,它仍然会产生同样的错误
  • 您确定需要在标识符前加上下划线吗?我很久没有做汇编了,但我记得并非总是如此,它取决于环境或参数的默认值。您可以通过 nm 检查 C 代码所期望的符号的真正样子。
  • 我刚刚用 nm 检查了它,它产生了00000000 T _Sum
【解决方案3】:

最好在 c 文件中声明 asm 内联。这是我自己的代码中的一个示例:

bool KxMutex::tryLock_i()
{
#ifdef KX_MUTEX_ASM
   int oldLock;
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
   asm volatile (
     "movl $1,%%eax\n\t"
     "xchg %%eax,%0\n\t"
     "movl %%eax,%1\n\t"
     : "=m" (mLock), "=m" (oldLock)
     :
     : "%eax", "memory"
   );
#elif defined(__GNUC__) && (defined(__ppc__))
   int newLock = 1;
   asm volatile (
     "\n1:\n\t"
     "lwarx  %0,0,%1\n\t"
     "cmpwi  0,%0,0\n\t"
     "bne-   2f\n\t"
     "stwcx. %2,0,%1\n\t"
     "bne-   1b\n\t"
     "isync\n"
     "2:\n\t"
     : "=&r" (oldLock)
     : "r" (&mLock), "r" (newLock)
     : "cr0", "memory"
  );
#endif
   return ( oldLock == 0 );
#else // !KX_MUTEX_ASM
   return ( pthread_mutex_trylock( (pthread_mutex_t*)this ) ? false : true );
#endif // !KX_MUTEX_ASM
}

有很多优点:

  1. 您不必自己管理堆栈帧、返回值等。
  2. 编译器可以在必要时内联函数,因为它控制调用约定
  3. 您可以直接在 .asm 代码中引用 c 语言符号
  4. 通过相同的 c 宏和定义控制不同平台的不同版本的 ASM 更容易。
  5. 所有 c 和 c++ 函数修饰符都有效 - extern、static、inline 等。
  6. 编译器仍然可以对函数参数进行类型检查,检查函数是否被正确调用等。
  7. 您可以根据需要使用 const 保护您的变量。

【讨论】:

    猜你喜欢
    • 2013-09-21
    • 2016-06-01
    • 2011-12-28
    • 2011-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-19
    相关资源
    最近更新 更多