Правильный встроенный код сборки для sys_uname - PullRequest
1 голос
/ 14 апреля 2019

Я написал встроенный ассемблерный код для системного вызова 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

Есть ли какая-то явная проблема, которой я не занимаюсь?Эта запись ничего не печатает, буквально "".

1 Ответ

2 голосов
/ 15 апреля 2019

В этом ответе предполагается, что есть причина, по которой вы хотите использовать системные вызовы напрямую, а не через 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;
}

При 32-битных системных вызовах FreeBSD параметры помещаются в стек в обратном порядке. Фиктивное значение (любое значение) должно быть помещено в стек перед выдачей int $0x80. Вам нужно настроить указатель стека ESP после системного вызова. Любые регистры, которые могут измениться, также должны быть обработаны. int $0x80 вернет код ошибки в EAX . Приведенный выше код возвращает это значение обратно в переменную syscallnum. Если вы изменяете регистр во встроенной сборке и не сообщаете GCC, это может привести к неопределенному поведению, которое часто трудно отследить.

Если вы передаете адреса через регистры, вам нужно будет добавить операнды памяти (даже если они являются фиктивными), чтобы указать, что данные в указателе в регистрах читаются и / или записываются. В качестве альтернативы вы можете указать memory clobber, который может быть проще для понимания, хотя это более грубый подход.

Встроенная сборка GCC является мощной, но ее трудно понять правильно и может вызвать неожиданное поведение, если вы ошибаетесь. Вы должны использовать только встроенную сборку как последнее средство . FreeBSD имеет функцию syscall , которую можно использовать для вызова большинства системных вызовов.

Вы могли бы написать встроенную сборку выше как:

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

Если вы попытаетесь запустить приведенный выше код, вы обнаружите, что он ничего не возвращает. Если вы используете программу TRUSS с такой командой, как truss ./progname, вы должны увидеть что-то подобное в выводе:

obs_uname (0xffffc6f8,0x0,0x0,0x0,0x0,0x0) ERR # 78 'Функция не реализована'

Это связано с тем, что FreeBSD 2+ не поддерживает системный вызов SYS_uname и теперь считается устаревшим. Libc uname из FreeBSD вызывает 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;
}
...