【问题标题】:Correct inline assembly code for sys_uname更正 sys_uname 的内联汇编代码
【发布时间】:2019-09-04 12:01:58
【问题描述】:

我已经为系统调用 sys_uname 编写了内联汇编代码,但它似乎不正确。

#include <sys/utsname.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscalls.h>
#include <string.h>

struct utsname stroj;
__asm__ volatile("pushl $0;"
              "pushl %%ebx;"
              "pushl $0;"
              "int $0x80;"
              :
              : "a" (SYS_uname), "b" (&stroj)
              );

//uname(&stroj); -> when I do this it works, but again, I want to use inline assembly
write(1, stroj.nodename, strlen(stroj.nodename)); // 1 = stdout

是否有一些我没有解决的明显问题?这个写什么都没有打印出来,字面意思是“”。

【问题讨论】:

  • 两次传递 $0 可能是错误的。也许其中一个应该是 1 美元。
  • 已编辑,应该是可编译的。我还尝试用 1 美元替换一个 0 美元,输出没有变化。
  • 我在 Unix、Intel 386、FreeBSD 上。我希望其中一个应该是您问题的答案
  • 这适用于 OS X 吗?你为什么在堆栈上压零?好的,是的,i386 FreeBSD 确实在用户空间堆栈上传递了系统调用参数。此外,您忘记了 "memory" 破坏或内存输出操作数,因此编译器将假定 stroj 仍未修改,并且它可以在编译时计算 strlen。您是否尝试过使用strace 或类似工具来跟踪系统调用?
  • 我没有使用 strace 的经验,这是我第一次尝试内联汇编。我将添加内存破坏器。

标签: c x86 freebsd inline-assembly i386


【解决方案1】:

这个答案假设您希望直接使用系统调用而不是通过 C 库函数是有原因的。

正确的内联汇编版本可能如下所示:

#include <sys/utsname.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <string.h>
#include <unistd.h>

/* SYS_uname has the value 164 */
/* #define SYS_uname 164 */

#define SYS_uname SYS_freebsd4_uname

int main()
{
    u_int syscallnum = SYS_uname;
    struct utsname stroj;

    asm("push %[stroj]\n\t"
        "push %%eax\n\t"        /* Required dummy value for int 0x80 */
        "int $0x80\n\t"
        "add $8, %%esp"         /* 2*4 bytes removed from stack */
        : "+a"(syscallnum)      /* error code also returned in syscallnum */
        : [stroj]"r"(&stroj)
        : "memory");

    write(1, stroj.nodename, strlen(stroj.nodename));
    return 0;
}

FreeBSD 32-bit system calls 参数以相反的顺序压入堆栈。在发出int $0x80 之前,必须将一个虚拟值(任何值)压入堆栈。系统调用后需要调整栈指针ESP。任何可能发生变化的寄存器也需要处理。 int $0x80 将在 EAX 中返回错误代码。上面的代码将该值返回到 syscallnum 变量中。如果您修改内联汇编中的寄存器并且不让 GCC 知道它可能会导致通常难以追踪的未定义行为。

如果您通过寄存器传递地址,则需要添加内存操作数(即使它们是哑元)以指定正在读取和/或写入寄存器中指针处的数据。或者,您可以指定memory clobber,尽管它是一种更暴力的方法,但它可能更容易理解。

GCC 的内联汇编功能强大,但很难正确使用,如果错误,可能会导致意外行为。您应该只将内联汇编用作last resort。 FreeBSD 有一个syscall function,可用于调用大多数系统调用。

你可以把上面的内联汇编写成:

asm(
    "push %[stroj]\n\t"
    "push %%eax\n\t"        /* Required dummy value for int 0x80 */
    "int $0x80\n\t"
    "add $8, %%esp"         /* 2*4 bytes removed from stack */
    : "+a"(syscallnum),     /* error code also returned in syscallnum */
      "=m"(stroj)
    : [stroj]"r"(&stroj));

FreeBSD 2+ 不支持过时的 SYS_uname

如果您尝试运行上面的代码,您会发现它没有返回任何内容。如果您将程序TRUSStruss ./progname 之类的命令一起使用,您应该在输出中看到如下内容:

obs_uname(0xffffc6f8,0x0,0x0,0x0,0x0,0x0) ERR#78 '功能未实现'

这是因为FreeBSD 2+ doesn't support the SYS_uname system call 现在被认为已过时。 FreeBSD 的 libc uname 调用 SYS___sysctl 来填充 utsname 结构的字段。从命令行您可以使用以下命令查询nodename

sysctl kern.hostname

你可以这样通过系统调用调用sysctl

#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysctl.h>

#define OSNAME_MAX_LEN 256
/* SYS___sysctl has the value 202 */
/* #define SYS___sysctl 202 */

int main(void)
{
    char        osname[OSNAME_MAX_LEN];
    size_t      osnamelen = sizeof(osname) - 1;
    int         name[] = {CTL_KERN, KERN_HOSTNAME};
    u_int       namelen = sizeof(name) / sizeof(name[0]);
    char *      old = osname;
    size_t *    oldlenp = &osnamelen;

    u_int syscallnum = SYS___sysctl;

    asm("push %[newlen]\n\t"
        "push %[new]\n\t"
        "push %[oldlenp]\n\t"
        "push %[old]\n\t"
        "push %[namelen]\n\t"
        "push %[name]\n\t"
        "push %%eax\n\t"         /* Required dummy value */
        "int $0x80\n\t"
        "add $28, %%esp"         /* 7*4=28 bytes to remove from stack */
        : "+a"(syscallnum)       /* error code also returned in syscallnum */
        : [name]"r"(name), [namelen]"r"(namelen),
          [old]"r"(old)  , [oldlenp]"r"(oldlenp),
          [new]"i"(NULL), [newlen]"i"(0)
        : "memory");

    if (syscallnum) {
        return EXIT_FAILURE;
    }

    osname[osnamelen]='\0';     /* Ensure the OS Name is Null terminated */
    printf("This machine's node name is %s\n", osname);
    return EXIT_SUCCESS;
}

当内联汇编调整 ESP (push 等) 时,它可能导致由 GCC 生成并通过约束传递的内存操作数指向错误的内存位置。如果将任何数据放在堆栈上,则尤其如此。为避免此问题,最简单的方法是通过寄存器传递地址。

使用syscall 函数而不是内联汇编也可以这样编写:

#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysctl.h>

#define OSNAME_MAX_LEN 256
/* SYS___sysctl has the value 202 */
/* #define SYS___sysctl 202 */

int main(void)
{
    char        osname[OSNAME_MAX_LEN];
    size_t      osnamelen = sizeof(osname) - 1;
    int         name[] = {CTL_KERN, KERN_HOSTNAME};
    u_int       namelen = sizeof(name) / sizeof(name[0]);
    char *      old = osname;
    size_t *    oldlenp = &osnamelen;

    if (syscall(SYS___sysctl, name, namelen, old, oldlenp, NULL, 0) == -1) {
        perror("sysctl");
        return EXIT_FAILURE;
    }

    osname[osnamelen]='\0';     /* Ensure the OS Name is Null terminated */
    printf("This machine's node name is %s\n", osname);
    return EXIT_SUCCESS;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-16
    • 2015-03-17
    • 2013-01-24
    • 2019-11-30
    • 1970-01-01
    相关资源
    最近更新 更多