提问者:小点点

Getrusage内联组装


我正在尝试使用套接字将getrusage函数实现到我的客户端服务器程序中,所有这些都在FreeBSD上运行。我想打印出处理器时间使用和内存使用。

我试图实现以下代码,但我得到输出非法指令(核心转储)

int getrusage(int who, struct rusage *usage){

    int errorcode;
    __asm__(
    "syscall"
    : "=a" (errorcode)
    : "a" (117), "D"  (who), "S" (usage)  //const Sysgetrusage : scno = 117
    : "memory"
    );

    if (errorcode<0) {
        printf("error");
    }
    return 1;  
}

更新:我尝试运行这个,但我得到零值或一些随机数值或负数值。任何想法我错过了什么?

int getrusage(int who, struct rusage *usage){

    int errorcode;
    __asm__("push $0;"
                "push %2;"
                "push %1;"
                "movl $117, %%eax;"
                "int $0x80;"
                :"=r"(errorcode)
                :"D"(who),"S"(usage)
                :"%eax"
        );

    if (errorcode<0) {
        printf("error");
    }
    return 1;

}

我想更有可能使用系统调用write,但它给了我一个编译警告:传递'strlen'的arg 1使指针来自整数而没有强制转换

编辑:(这是现在的工作代码,关于评论)

struct rusage usage;
getrusage(RUSAGE_SELF,&usage);
char tmp[300];

write(i, "Memory: ", 7);
sprintf (tmp, "%ld", usage.ru_maxrss);
write(i, tmp, strlen(tmp));
write(i, "Time: ", 5);
sprintf (tmp, "%lds", usage.ru_utime.tv_sec);
write(i, tmp, strlen(tmp));
sprintf (tmp, "%ldms", usage.ru_utime.tv_usec);
write(i, tmp, strlen(tmp));

知道什么是错的吗?


共1个答案

匿名用户

您收到非法指令错误的原因是SYSCALL指令仅在运行64位程序的64位FreeBSD上可用。这是一个严重的问题,因为您的一条评论表明您的代码正在32位FreeBSD上运行。

在正常情况下,您不需要编写自己的getrusage,因为它是该平台上C库(libc)的一部分。似乎您的任务是使用内联汇编来完成它。

由于SYSCALL会破坏RCX和R11的内容,您的64位代码有点bug。您的代码可能会工作,但将来可能会失败,尤其是当程序扩展并启用优化时。以下更改将这2个寄存器添加到垃圾列表中:

int errorcode;
    __asm__(
        "syscall"
        : "=a" (errorcode)
        : "a" (117), "D"  (who), "S" (usage)  //const Sysgetrusage : scno = 117
        : "memory", "rcx", "r11"
);

使用内存阻塞器会导致生成低效代码,因此我仅在必要时使用它。随着您成为专家,可以消除对内存阻塞器的需求。如果不允许我使用getrusage的C库版本,我会使用如下函数:

int getrusage(int who, struct rusage *usage){

    int errorcode;
    __asm__(
        "syscall"
        : "=a"(errorcode), "=m"(*usage)
        : "0"(117), "D"(who), "S"(usage)
        : "rcx", "r11"
    );

    if (errorcode<0) {
        printf("error");
    }
    return errorcode;
}

这使用内存操作数作为输出约束并丢弃内存阻塞器。由于编译器知道rusage结构有多大并且是=m表示输出约束修改了我们不需要的内存,需要内存阻塞器。

正如注释和更新代码中提到的,要在FreeBSD中使用32位代码进行系统调用,您必须使用int 0x80。这在FreeBSD系统调用约定中有所描述。参数从右向左压入堆栈,您必须在压入最后一个参数后将任何4字节值压入堆栈,从而在堆栈上分配4个字节。

您编辑的代码有一些错误。首先您在其余参数之前推送额外的4个字节。您需要在之后推送它。您需要在int 0x80之后调整堆栈,以有效地回收传递的参数使用的堆栈空间。您在堆栈上推送了三个4字节值,因此您需要在int 0x80之后添加12ESP。

你还需要一个内存重击器,因为编译器根本不知道你实际上修改了内存。这是因为你做约束的方式变量用法中的数据被修改了,但编译器不知道是什么。

int 0x80的返回值将在EAX中,但您使用约束=r。它应该是=a,因为返回值将在EAX中返回。由于使用=a告诉编译器EAX已被破坏,因此您无需再将其列为破坏者。

修改后的代码可能如下所示:

int getrusage(int who, struct rusage *usage){

    int errorcode;
    __asm__("push %2;"
            "push %1;"
            "push $0;"
            "movl $117, %%eax;"
            "int $0x80;"
            "add $12, %%esp"
            :"=a"(errorcode)
            :"D"(who),"S"(usage)
            :"memory"
            );

    if (errorcode<0) {
        printf("error");
    }
    return errorcode;
}

另一种可以用更先进的技术编写的方法是:

int getrusage(int who, struct rusage *usage){

    int errorcode;
    __asm__("push %[usage]\n\t"
            "push %[who]\n\t"
            "push %%eax\n\t"
            "int $0x80\n\t"
            "add $12, %%esp"
            :"=a"(errorcode), "=m"(*usage)
            :"0"(117), [who]"ri"(who), [usage]"r"(usage)
            :"cc" /* Don't need this with x86 inline asm but use for clarity */
        );

    if (errorcode<0) {
        printf("error");
    }
    return errorcode;
}

有关扩展内联汇编程序的更多信息,请参见GCC留档。