【问题标题】:Assembly: How to return a pointer?程序集:如何返回指针?
【发布时间】:2021-07-04 23:11:51
【问题描述】:

inventory 函数接受一个设备指针数组并调用评估来找出变化是什么。然后inventory函数返回一个变化最大的指针。

unsigned short evaluate(Struct Device *thing);

struct Device *inventory(struct Device *things[], int count);

设备.h:

struct Device{
   char name[20];
   short adjustments[8];
   short avg;
}

main.c:

#include <stdio.h>
#include <stdlib.h>

#include "device.h"

/* from device.h, shown here to make it easier to code
struct Device
{   
    char name[20];
    short adjustments[8];
    short avg;
};
*/

struct Device things[] =
{
    { "Museum Quality",{ 0, 1, 0, -1, 0, -1, 0, 1}, 1 },
    { "Fell off truck",
        { 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff},
    2 },

    /* the casts are there since C constants are ints and you can't make
     * them shorts and negative numbers look like overflow */
    { "Fell down stairs",
    { 0x7ff0, (unsigned short)0x800f, 0x7ffe, (unsigned short)0x8001, 
    0x7ffd, (unsigned short)0x8002, 0x7fff, 0x7f00}, 
    3 },

    { "On left side",{ 10, 11, 10, 12, 10, 13, 10, 14}, 4 },
    { "On right side",{ -300, -321, -320, -332, -320, -313, -310, -314}, 5 }
};


/*
struct Device *inventory(struct Device *things[], int count);
*/

int main()
{

    struct Device *pointers[1 + sizeof(things) / sizeof(things[0])] 
        = {NULL};
    const int count =  sizeof(things) / sizeof(things[0]);

    struct Device *worst = NULL;

    int i;


    for (i=0; i< count; i++)
    {
        printf("Loading %s into pointers\n", things[i].name);
        pointers[i] = &things[i];
    }
    pointers[i] = NULL;

    worst = inventory(pointers, count);

    printf("main: The worst is %s\n", worst->name);


    return(EXIT_SUCCESS);
}

calibrate.s: # 计算平均值并返回可变性

    movq %r11, %rax
    sarq $3, %rax
    movl %eax, 36(%rdi)
    subq %rcx, %rdx
    movq %rdx, %rax

evaluate.s: # 调用校准并返回可变性

        call calibrate

inventory.s:

rdi 是保存设备指针数组的参数,#rbx 是 rdi 的副本,因此它存储设备指针数组,#r12 存储有效指针的计数,#r14 存储最大变化,#r15 存储变化最大的设备指针。

        movq %rdi, %r15         #r15 stores pointer with most variable

   loop: 
        movq (%rbx,%r12,8), %rdi
        call evaluate
        
        cmpq %r14, %rax
        jle skipmax
        movq %rax, %r14
        movq (%rbx, %r12,8), %r15
 skipmax:
        decq %r12
        cmpq $0, %r12
        jge loop

        movq %r13, %rsi
        movq $.LC0, %rdi
        movq $0, %rax
        call printf
            
        movq %r15, %rdx
        movq %r14, %rsi
        movq $.LC1, %rdi
        movq $0, %rax
        call printf

        movq %r15, %rax
                

我运行程序,结果是:r14 正确地获得了最高变异性,但 LC0 打印了正确的最高变异性 (r14) 但名称为空 (r15),并且试图打印存储在 rax 中的名称的 C 程序是也是空的,打印语句打印:

最大变化是 65534 距离

相反,它应该打印:

最大变化是从楼梯摔下来的 65534

正如您从上面看到的那样,名称没有被打印出来。

经过进一步的故障排除,我发现r15的指针是正确的,它的调整值和平均值都是正确的,只是name是空字符串,知道为什么吗?也许是因为名称是一个字符数组,并且没有正确复制?

谢谢!

【问题讨论】:

  • 你试过单步调试器吗?
  • 你确定 evaluate 遵守 ABI 并且不会破坏 R15,并且它返回一个 64 位整数正确符号扩展为 RAX,而不是 intshort 或什么?您确定要设备指针,而不是指向设备指针的指针(即指向数组条目的指针)吗?如果是后者,lea 而不是 mov。如果您确实想要前者,那么我认为错误不在此代码中。
  • 请把它变成一个minimal reproducible example,为这个函数以及evaluate(至少一个存根)提供完整的可构建代码,以及一个main函数,通过适当的输入调用它们.我同意 Peter 的观点,我在这里没有看到任何错误,因此错误必须在您没有向我们展示的代码中。缺少这样的例子就是为什么这个问题已经有 2 票赞成,还有 1 票会反对。
  • 并立即确定了罪魁祸首:movl %eax, 36(%rdi) in calibrate。它应该写入rdi-&gt;avg,但它是 32 位移动,avg 是 16 位,因此接下来的 2 个字节会被覆盖。让它movw %ax, 36(%rdi)。所以你猜怎么着:这个 bug 不是你想的那样,盯着你最初发布的代码是徒劳的,在完整的代码上运行调试器是相当有效的。
  • @EmperorHan:呃,StackOverflow 真的希望你不要删除它,因为它会使帖子对其他人无用。理想情况下,您应该将代码缩减为可以安全共享的内容。您至少可以留下足够的空间,以便可以看到错误及其原因吗?

标签: loops pointers assembly x86-64


【解决方案1】:

错误

错误是movl %eax, 36(%rdi)calibrate.s 的第 38 行。这显然应该写入相关Deviceavg 成员,但它是32 位存储,Device::avg 是16 位short。所以应该是movw %ax, 36(%rdi)

我是如何使用 gdb 找到它的

希望这将提供一些关于 gdb 可以做什么以及如何有效使用它的信息。

我在inventory 中的第二个printf 处设置了一个断点,此时我做了x/s $rdx 以查看%rdx 指向的字符串:

(gdb) x/s $rdx
0x4040ac <things+76>:   ""

好的,所以它是一个空字符串,但它位于things 内的偏移量76,您可以检查它是things[2].name 的地址。这不应该是一个空字符串,所以让我们看看things发生了什么。

(gdb) p things
$4 = {{name = "Museum Quality\000\000\000\000\000", adjustments = {0, 1, 0, -1, 0, -1, 0, 1}, 
    avg = 0}, {name = "\000\000ll off truck\000\000\000\000\000", adjustments = {32767, 32767, 32767, 
      32767, 32767, 32767, 32767, 32767}, avg = 32767}, {name = "\000\000ll down stairs\000\000\000", 
    adjustments = {32752, -32753, 32766, -32767, 32765, -32766, 32767, 32512}, avg = 8159}, {
    name = "\000\000 left side\000\000\000\000\000\000\000", adjustments = {10, 11, 10, 12, 10, 13, 
      10, 14}, avg = 11}, {name = "\000\000 right side\000\000\000\000\000\000", adjustments = {-300, 
      -321, -320, -332, -320, -313, -310, -314}, avg = -317}}

嗯,things[2].name 的前两个字节现在为空,就此而言,其他几个字节也是空的。它们不是这样初始化的,而且名称根本不应该被修改,那是怎么发生的呢?一个观察点会告诉我们应该归咎于哪条指令。

(gdb) watch things[1].name[0]
Hardware watchpoint 3: things[1].name[0]
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
...
Hardware watchpoint 3: things[1].name[0]

Old value = 70 'F'
New value = 0 '\000'
calibrate () at calibrate.s:39
39          subq %rcx, %rdx

注意,这是触发观察点的指令之后。所以我们回到第 38 行,想了一会儿,就到了。

【讨论】:

  • 这太棒了!堆栈溢出很容易成为我最喜欢问计算机科学相关问题的地方!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-25
相关资源
最近更新 更多