尝试打印双精度程序时程序崩溃(但其他类型都可以)

时间:2017-08-08 09:01:03

标签: c linux assembly x86-64

我试图从汇编程序代码(Linux x86-64)调用C函数。在汇编代码中,类型由3 LSB编码。 C函数的目的是简单地打印键入的值。这是功能:

int print(long i) {
  int type = i & 7;
  long val = i >> 3;
  double c;
  switch(type) {
  case 0:
    printf("%d\n", val);
    break;
  case 1:
    if (!val)
      printf("false\n");
    else if (val == 1) 
      printf("true\n");
    else if (val == 2)
      printf("nil\n");
    break;
  case 2:
    printf("%s\n", (char*)(val + 8));
    break;
  case 6:
    c = *(double*) val;
    //printf("%d\n", (int)c); /* no seg fault */
    //printf("%f\n", 1.0); /* seg fault */
    printf("%f\n", c); /* seg fault */
    break;
  }
  return 1;
}

到目前为止,我一直在使用字符串指针,所以我知道它不是内存问题,但我第一次使用%xmm寄存器,所以我可能正在做某事错在那里。这是相关的呼叫段:

    movsd   _DB1(%rip), %xmm4
    movsd   _DB2(%rip), %xmm5
    addsd   %xmm5, %xmm4
    movsd   %xmm4, (%r12)
    leaq    6(, %r12, 8), %rdi
    addq    $8, %r12
    call    print
##### ... #####
    .data
_DB1:
    .double 2
_DB2:
    .double 3

%r12指向堆中分配的内存。最奇怪的是,如果我在调用printf之前将double转换为整数,则没有分段错误(在情况6中取消注释行并在下面注释该行)并且它得出正确的结果(5) 。在调试过程中,我发现在printf函数的子例程中发生了分段错误,我无法看到代码。即使我定义了一个随机双,我也无法打印它。

此时任何帮助都将不胜感激,谢谢。

1 个答案:

答案 0 :(得分:2)

代码片段的行为取决于指向double和字符串的指针如何编码为longi以及其他考虑因素。它非常不便携。

您为long val = i >> 3;的负值计算具有实现定义行为的i,并在以后转换为{{1时生成具有01高位的指针值}或(double*)。虽然在常见架构中忽略了指针表示中的一些高阶位,但依赖此行为是不安全的,并且在32位intel PC上会失败。最好确保指针在8字节边界上对齐(至少),然后屏蔽低位,或者在转换为(char*)(double*)之前调整值。

如果(char*)小于uintptr_t,使用long代替long也会更加轻松,就像在64位Windows系统上一样。