使用内联汇编时出现分段错误(核心转储)错误
segmentation fault(core dumped) error while using inline assembly
我在 GCC 中使用内联汇编。我想将一个变量内容向左旋转2位(我把变量移到rax寄存器然后旋转2次)。我写了下面的代码,但我遇到了分段错误(核心转储)错误。
如果你能帮助我,我将不胜感激。
uint64_t X = 13835058055282163712U;
asm volatile(
"movq %0 , %%rax\n"
"rol %%rax\n"
"rol %%rax\n"
:"=r"(X)
:"r"(X)
);
printf("%" PRIu64 "\n" , X);
理解内联 asm 的关键是理解每个 asm 语句有两部分:
实际汇编程序的文本,编译器将在其中进行文本替换,但不理解。
这是文档中的 AssemblerTemplate(__asm__()
中第一个 :
之前的所有内容)。
关于汇编程序的描述,编译器理解。
: OutputOperands : InputOperands : Clobbers
the documentation.
这必须告诉编译器汇编器如何适应编译器围绕它生成的所有代码。代码生成忙于分配寄存器来保存值、决定以何种顺序执行操作、将操作移出循环、消除未使用的代码片段、丢弃不再需要的值,等等。
实际的汇编器是一个黑匣子,它接受这里描述的输入,产生描述的输出,作为副作用可能 'clobber' 一些寄存器 and/or 内存。 必须 是对汇编程序所做工作的完整描述...否则编译器围绕您的模板生成的 asm 将与其发生冲突并依赖于错误的假设。
有了这些信息,编译器 可以决定汇编程序可以使用哪些寄存器,您应该让它这样做。
那么,您的片段:
asm volatile(
"movq %0 , %%rax\n"
"rol %%rax\n"
"rol %%rax\n"
:"=r"(X)
:"r"(X)
);
有几个"issues":
- 您可能已经选择了
%rax
作为结果,因为 asm() 就像一个函数,并且可能期望 return 在 %rax
中得到结果 --但事实并非如此。
- 你继续使用
%rax
,编译器可能(很好)已经分配给其他东西......所以你实际上是 'clobbering' %rax
但你未能将其告知编译器!
- 您指定了
=r(X)
(OutputOperand),它告诉编译器期望某个寄存器中的输出,并且该输出将是变量的新值 X
. AssemblerTemplate 中的 %0
将替换为为输出选择的寄存器。可悲的是,您的程序集将 %0
作为输入 :-( 实际上,输出在 %rax
中——如上所述,编译器并不知道。
您还指定了 r(X)
(InputOperand),它告诉编译器将变量 X
的当前值安排为放在一些寄存器中供汇编器使用。这将是 AssemblerTemplate 中的 %1
。遗憾的是,您的程序集不使用此输入。
即使输出和输入操作数都引用 X
,编译器可能不会使 %0
与 %1
相同的寄存器。 (这允许它使用 asm 块作为非破坏性操作,使输入的原始值保持不变。如果这不是您的模板的工作方式,请不要那样写。
- 当所有输入和输出都由约束正确描述时,通常您不需要
volatile
。如果(所有)输出未被使用,编译器将做的一件好事是丢弃 asm()
... volatile
告诉编译器不要这样做(并告诉它一个其他一些事情......请参阅手册)。
除此之外,一切都很棒。以下是安全的,并且避免了 mov
指令:
asm("rol %0\n"
"rol %0\n" : "+r"(X));
其中 "+r"(X)
表示需要一个组合的输入和输出寄存器,采用 X
的旧值和 returning 一个新值。
现在,如果您不想替换 X
,那么假设 Y
是结果,您可以:
asm("mov %1, %0\n"
"rol %0\n"
"rol %0\n" : "=r"(Y) : "r"(X));
但最好让编译器来决定它是否需要 mov
或者它是否可以让输入被销毁。
有一些关于 InputOperands 的规则值得一提:
汇编器不得覆盖任何InputOperands——编译器正在跟踪它在注册,并期望 InputOperands 被保留。
编译器期望在 any OutputOperand[=138= 之前读取所有 InputOperands ] 写的。当编译器知道给定的 InputOperand 在 asm()
之后不再使用时,这一点很重要,因此它可以分配 InputOperand的注册到 OutputOperand。有一种叫做 earlyclobber (=&r(foo)
) 的东西可以解决这个小问题。
在上面,如果您实际上没有再次使用 X
,编译器可能会将 %0
和 %1
分配给同一个寄存器!但是(冗余的)mov
仍然会被汇编——记住编译器真的不理解 AssemblerTemplate。所以,你通常最好在 C 中改组值,而不是 asm()
。参见 https://gcc.gnu.org/wiki/DontUseInlineAsm and Best practices for circular shift (rotate) operations in C++
所以这是一个主题的四个变体,以及生成的代码 (gcc -O2):
// (1) uses both X and Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi # address of format string
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi # X = 99
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # 2nd asm, compiler using it as a copy-and-rotate
) ; rol %rdx
rol %rdx
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt> # tailcall printf
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (2) uses both X and Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # compiler-generated mov
) ; rol %rdx # 2nd asm
rol %rdx
Y = X ; jmpq 0x4010a0 <printf@plt>
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (3) uses only Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rsi # redundant instruction because of mov in the asm template
) ; rol %rsi
rol %rsi
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt>
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx\n", Y) ;
}
// (4) uses only Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) rol %rsi # no wasted mov, compiler picked %0=%1=%rsi
) ; rol %rsi
jmpq 0x4010a0 <printf@plt>
Y = X ;
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx\n", Y) ;
}
这有望证明编译器忙于将值分配给寄存器,跟踪它需要保留哪些值,最大限度地减少 register/register 移动,并且通常很聪明。
所以诀窍是与编译器一起工作,理解 :
OutputOperands:
InputOperands :
Clobbers 是您描述汇编器正在做什么的地方。
我在 GCC 中使用内联汇编。我想将一个变量内容向左旋转2位(我把变量移到rax寄存器然后旋转2次)。我写了下面的代码,但我遇到了分段错误(核心转储)错误。 如果你能帮助我,我将不胜感激。
uint64_t X = 13835058055282163712U;
asm volatile(
"movq %0 , %%rax\n"
"rol %%rax\n"
"rol %%rax\n"
:"=r"(X)
:"r"(X)
);
printf("%" PRIu64 "\n" , X);
理解内联 asm 的关键是理解每个 asm 语句有两部分:
实际汇编程序的文本,编译器将在其中进行文本替换,但不理解。
这是文档中的 AssemblerTemplate(
__asm__()
中第一个:
之前的所有内容)。关于汇编程序的描述,编译器理解。
: OutputOperands : InputOperands : Clobbers
the documentation.这必须告诉编译器汇编器如何适应编译器围绕它生成的所有代码。代码生成忙于分配寄存器来保存值、决定以何种顺序执行操作、将操作移出循环、消除未使用的代码片段、丢弃不再需要的值,等等。
实际的汇编器是一个黑匣子,它接受这里描述的输入,产生描述的输出,作为副作用可能 'clobber' 一些寄存器 and/or 内存。 必须 是对汇编程序所做工作的完整描述...否则编译器围绕您的模板生成的 asm 将与其发生冲突并依赖于错误的假设。
有了这些信息,编译器 可以决定汇编程序可以使用哪些寄存器,您应该让它这样做。
那么,您的片段:
asm volatile(
"movq %0 , %%rax\n"
"rol %%rax\n"
"rol %%rax\n"
:"=r"(X)
:"r"(X)
);
有几个"issues":
- 您可能已经选择了
%rax
作为结果,因为 asm() 就像一个函数,并且可能期望 return 在%rax
中得到结果 --但事实并非如此。 - 你继续使用
%rax
,编译器可能(很好)已经分配给其他东西......所以你实际上是 'clobbering'%rax
但你未能将其告知编译器! - 您指定了
=r(X)
(OutputOperand),它告诉编译器期望某个寄存器中的输出,并且该输出将是变量的新值X
. AssemblerTemplate 中的%0
将替换为为输出选择的寄存器。可悲的是,您的程序集将%0
作为输入 :-( 实际上,输出在%rax
中——如上所述,编译器并不知道。 您还指定了
r(X)
(InputOperand),它告诉编译器将变量X
的当前值安排为放在一些寄存器中供汇编器使用。这将是 AssemblerTemplate 中的%1
。遗憾的是,您的程序集不使用此输入。即使输出和输入操作数都引用
X
,编译器可能不会使%0
与%1
相同的寄存器。 (这允许它使用 asm 块作为非破坏性操作,使输入的原始值保持不变。如果这不是您的模板的工作方式,请不要那样写。- 当所有输入和输出都由约束正确描述时,通常您不需要
volatile
。如果(所有)输出未被使用,编译器将做的一件好事是丢弃asm()
...volatile
告诉编译器不要这样做(并告诉它一个其他一些事情......请参阅手册)。
除此之外,一切都很棒。以下是安全的,并且避免了 mov
指令:
asm("rol %0\n"
"rol %0\n" : "+r"(X));
其中 "+r"(X)
表示需要一个组合的输入和输出寄存器,采用 X
的旧值和 returning 一个新值。
现在,如果您不想替换 X
,那么假设 Y
是结果,您可以:
asm("mov %1, %0\n"
"rol %0\n"
"rol %0\n" : "=r"(Y) : "r"(X));
但最好让编译器来决定它是否需要 mov
或者它是否可以让输入被销毁。
有一些关于 InputOperands 的规则值得一提:
汇编器不得覆盖任何InputOperands——编译器正在跟踪它在注册,并期望 InputOperands 被保留。
编译器期望在 any OutputOperand[=138= 之前读取所有 InputOperands ] 写的。当编译器知道给定的 InputOperand 在
asm()
之后不再使用时,这一点很重要,因此它可以分配 InputOperand的注册到 OutputOperand。有一种叫做 earlyclobber (=&r(foo)
) 的东西可以解决这个小问题。
在上面,如果您实际上没有再次使用 X
,编译器可能会将 %0
和 %1
分配给同一个寄存器!但是(冗余的)mov
仍然会被汇编——记住编译器真的不理解 AssemblerTemplate。所以,你通常最好在 C 中改组值,而不是 asm()
。参见 https://gcc.gnu.org/wiki/DontUseInlineAsm and Best practices for circular shift (rotate) operations in C++
所以这是一个主题的四个变体,以及生成的代码 (gcc -O2):
// (1) uses both X and Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi # address of format string
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi # X = 99
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # 2nd asm, compiler using it as a copy-and-rotate
) ; rol %rdx
rol %rdx
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt> # tailcall printf
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (2) uses both X and Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # compiler-generated mov
) ; rol %rdx # 2nd asm
rol %rdx
Y = X ; jmpq 0x4010a0 <printf@plt>
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (3) uses only Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rsi # redundant instruction because of mov in the asm template
) ; rol %rsi
rol %rsi
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt>
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx\n", Y) ;
}
// (4) uses only Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov [=13=]x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov [=13=]x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) rol %rsi # no wasted mov, compiler picked %0=%1=%rsi
) ; rol %rsi
jmpq 0x4010a0 <printf@plt>
Y = X ;
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx\n", Y) ;
}
这有望证明编译器忙于将值分配给寄存器,跟踪它需要保留哪些值,最大限度地减少 register/register 移动,并且通常很聪明。
所以诀窍是与编译器一起工作,理解 :
OutputOperands:
InputOperands :
Clobbers 是您描述汇编器正在做什么的地方。