Какова семантика заполнения / упаковки структуры в ядре Linux? - PullRequest
2 голосов
/ 09 ноября 2019

Меня интересует семантика заполнения и упаковки структуры, особенно в отношении структур, возвращаемых из ядра Linux.

Например, если программа + stdlib скомпилирована, поэтому заполнение структуры не требуетместо, и ядро ​​скомпилировано с таким заполнением структуры , что имеет место (что в любом случае IIRC является GCC по умолчанию), конечно, программа не может работать из-за того, что структуры, возвращаемые ядром, являются мусором из его точкиview.

Как насчет того, если рассматриваемый компилятор со временем изменил свою семантику дополнения, наверняка та же проблема может возникнуть. Структуры, определенные в /usr/include/linux/* и /usr/include/asm-generic/*, по-видимому, не упакованы, поэтому они зависят от используемого компилятора и семантики выравнивания указанного компилятора, верно?

Но я могу взять двоичный файл, скомпилированный несколько лет назадна другом компьютере с другими требованиями к выравниванию памяти и предположительно другой семантикой заполнения, и запустите его на моем современном компьютере, и он, кажется, работает нормально.

Как он не видит мусор? Это просто чистая удача? Заботятся ли авторы компилятора (например, TCC и т.п.) о копировании семантики заполнения структуры GCC? Как эта потенциальная проблема решается в реальном мире?

1 Ответ

1 голос
/ 10 ноября 2019

Структуры, определенные в /usr/include/linux/* и /usr/include/asm-generic/*, не кажутся упакованными, поэтому они зависят от используемого компилятора и семантики выравнивания указанного компилятора, верно?

В общем, это не так. Вот пример из GCC на 64-битной Ubuntu (/usr/include/x86_64-linux-gnu/asm/stat.h):

struct stat {
        __kernel_ulong_t        st_dev;
        __kernel_ulong_t        st_ino;
        __kernel_ulong_t        st_nlink;

        unsigned int            st_mode;
        unsigned int            st_uid;
        unsigned int            st_gid;
        unsigned int            __pad0;
        __kernel_ulong_t        st_rdev;
        __kernel_long_t         st_size;
        __kernel_long_t         st_blksize;
        __kernel_long_t         st_blocks;      /* Number 512-byte blocks allocated. */

        __kernel_ulong_t        st_atime;
        __kernel_ulong_t        st_atime_nsec;
        __kernel_ulong_t        st_mtime;
        __kernel_ulong_t        st_mtime_nsec;
        __kernel_ulong_t        st_ctime;
        __kernel_ulong_t        st_ctime_nsec;
        __kernel_long_t         __unused[3];
};

Смотрите __pad0? int обычно составляет 4 байта, но st_rdev равен long, что составляет 8 байтов, поэтому он должен быть выровнен по 8 байтов. Однако ему предшествует 3 дюйма = 12 байтов, поэтому добавляется 4 байта __pad0.

По сути, реализация stdlib заботится о жестком кодировании своего ABI.

НО , что не верно для всех API. Вот struct flock (с той же машины, /usr/include/asm-generic/fcntl.h), используемый вызовом fcntl():

struct flock {
    short   l_type;
    short   l_whence;
    __kernel_off_t  l_start;
    __kernel_off_t  l_len;
    __kernel_pid_t  l_pid;
    __ARCH_FLOCK_PAD
};

Как видите, между l_whence и l_start нет заполнения. И действительно, для следующей программы на C, сохраненной как abi.c:

#include <fcntl.h>
#include <string.h>

int main(int argc, char **argv)
{
    struct flock fl;
    int fd;

    fd = open("y", O_RDWR);
    memset(&fl, 0xff, sizeof(fl));
    fl.l_type = F_RDLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 200;
    fl.l_len = 1;
    fcntl(fd, F_SETLK, &fl);
}

Мы получаем:

$ cc -g -o abi abi.c && strace -e fcntl ./abi
fcntl(3, F_SETLK, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=200, l_len=1}) = 0
+++ exited with 0 +++
$ cc -g -fpack-struct -o abi abi.c && strace -e fcntl ./abi
fcntl(3, F_SETLK, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=4294967296, l_len=-4294967296}) = 0
+++ exited with 0 +++

Как видите, поля, следующие за l_whence, действительно являются мусором.

Более того, C не имеет ABI , и поэтому эта хрупкая совместимость зависит от хорошей реализации игры. struct stat выше предполагает, что компилятор не будет вставлять дополнительные случайные отступы.

ANSI C говорит:

Там также может быть безымянное заполнение в конце структуры или объединения,при необходимости, чтобы добиться правильного выравнивания, чтобы структура или объединение были частью массива.

Нет формулировки о том, как заполнение может быть вставлено в середину структурыпо причинам, отличным от выравнивания, однако есть также:

Поведение, определяемое реализацией

Каждая реализация должна документировать свое поведение в каждой из областей, перечисленных в этом разделе. Следующее определяется реализацией:

...

Заполнение и выравнивание элементов структур. Это не должно создавать проблем, если двоичные данные, записанные одной реализацией, не прочитаны другой.

На моей машине с Ubuntu и компилятор, и стандартная библиотека поступают из GCC, поэтому они бесперебойно взаимодействуют. Clang хочет расти, поэтому он совместим с GNU libc. Все просто играют хорошо, большую часть времени.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...