Как я могу форсировать размер int для целей отладки? - PullRequest
22 голосов
/ 01 апреля 2019

У меня есть две сборки для части программного обеспечения, которую я разрабатываю, одна для встроенной системы, где размер int равен 16 битам, а другая для тестирования на настольном компьютере, где размер int равен 32 битам. Я использую целочисленные типы фиксированной ширины от <stdint.h>, но правила целочисленного продвижения по-прежнему зависят от размера целого.

В идеале я хотел бы, чтобы следующий код печатал 65281 (целочисленное повышение до 16 бит) вместо 4294967041 (целочисленное повышение до 32 бит) из-за целочисленного продвижения, чтобы оно точно соответствовало поведению на Встроенная система. Я хочу быть уверен, что код, который дает один ответ во время тестирования на моем рабочем столе, дает точно такой же ответ во встроенной системе. Решение для GCC или Clang было бы хорошо.

#include <stdio.h>
#include <stdint.h>

int main(void){
    uint8_t a = 0;
    uint8_t b = -1;

    printf("%u\n", a - b);

    return 0;
}

EDIT:

Пример, который я привел, возможно, не был лучшим примером, но я действительно хочу, чтобы целочисленное продвижение было до 16 бит вместо 32 бит. Возьмите следующий пример:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void){
    uint16_t a = 0;
    uint16_t b = 1;
    uint16_t c = a - 2; // "-2": 65534
    uint16_t d = (a - b) / (a - c);

    printf("%" PRIu16 "\n", d);

    return 0;
}

Вывод 0 в 32-битной системе из-за усечения от целочисленного деления после перехода к (подписанному) целому, в отличие от 32767.

Лучший ответ на данный момент, похоже, заключается в использовании эмулятора, на который я не надеялся, но я думаю, что он имеет смысл. Похоже, что для компилятора теоретически должно быть возможно сгенерировать код, который ведет себя так, как будто размер int равен 16 битам, но я думаю, что, возможно, не должно быть слишком удивительным, что на практике нет простого способа сделать это, и, вероятно, нет особого спроса на такой режим и какую-либо необходимую поддержку во время выполнения.

РЕДАКТИРОВАТЬ 2:

Это то, что я исследовал до сих пор: на самом деле существует версия GCC, которая нацелена на i386 в 16-битном режиме на https://github.com/tkchia/gcc-ia16. Выходные данные - это файл DOS COM, который можно запустить в DOSBox. Например, два файла:

test.c

#include <stdint.h>

uint16_t result;

void test16(void){
    uint16_t a = 0;
    uint16_t b = 1;
    uint16_t c = a - 2; // "-2": 65534
    result = (a - b) / (a - c);
}

main.c

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

extern uint16_t result;
void test16(void);

int main(void){
    test16();
    printf("result: %" PRIu16"\n", result);

    return 0;
}

может быть скомпилировано с

$ ia16-elf-gcc -Wall test16.c main.c -o a.com

для получения a.com, который можно запустить в DOSBox.

D:\>a
result: 32767

Если взглянуть немного дальше, ia16-elf-gcc на самом деле создает 32-битный эльф в качестве промежуточного звена, хотя по умолчанию итоговая ссылка выводится в виде файла COM:

$ ia16-elf-gcc -Wall -c test16.c -o test16.o
$ file test16.o
test16.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

Я могу принудительно связать его с main.c, скомпилированным с обычным GCC, но неудивительно, что в результате получаются исполняемые ошибки segfaults.

$ gcc -m32 -c main.c -o main.o
$ gcc -m32 -Wl,-m,elf_i386,-s,-o,outfile test16.o main.o
$ ./outfile
Segmentation fault (core dumped)

Из поста здесь кажется, что теоретически должно быть возможно связать вывод 16-битного кода из ia16-elf-gcc с 32-битным кодом, хотя я не совсем уверен, как , Кроме того, существует проблема запуска 16-разрядного кода в 64-разрядной ОС. Более идеальным был бы компилятор, который все еще использует обычные 32-битные / 64-битные регистры и инструкции для выполнения арифметики, но эмулирует арифметику посредством библиотечных вызовов, аналогично тому, как, например, uint64_t эмулируется на (не-64- немного) микроконтроллер.

Самое близкое, что я мог бы найти для фактического запуска 16-битного кода на x86-64, это здесь , и это кажется экспериментальным / полностью не поддерживаемым. На данный момент, просто использование эмулятора начинает казаться лучшим решением, но я подожду немного дольше и посмотрю, есть ли у кого-нибудь еще идеи.

РЕДАКТИРОВАТЬ 3

Я собираюсь пойти дальше и принять ответ Антти, хотя это не тот ответ, который я надеялся услышать. Если кому-то интересно узнать, что выводит ia16-elf-gcc (я никогда раньше не слышал о ia16-elf-gcc), вот разборка:

$ objdump -M intel -mi386 -Maddr16,data16 -S test16.o > test16.s

Обратите внимание, что вы должны указать, что это 16-битный код, в противном случае objdump интерпретирует его как 32-битный код, который сопоставляется с различными инструкциями (см. Далее).

test16.o:     file format elf32-i386


Disassembly of section .text:

00000000 <test16>:
0:  55                      push   bp     ; save frame pointer
1:  89 e5                   mov    bp,sp  ; copy SP to frame pointer
3:  83 ec 08                sub    sp,0x8 ; allocate 4 * 2bytes on stack
6:  c7 46 fe 00 00          mov    WORD PTR [bp-0x2],0x0 ; uint16_t a = 0
b:  c7 46 fc 01 00          mov    WORD PTR [bp-0x4],0x1 ; uint16_t b = 1
10: 8b 46 fe                mov    ax,WORD PTR [bp-0x2]  ; ax = a
13: 83 c0 fe                add    ax,0xfffe             ; ax -= 2
16: 89 46 fa                mov    WORD PTR [bp-0x6],ax  ; uint16_t c = ax = a - 2
19: 8b 56 fe                mov    dx,WORD PTR [bp-0x2]  ; dx = a
1c: 8b 46 fc                mov    ax,WORD PTR [bp-0x4]  ; ax = b
1f: 29 c2                   sub    dx,ax                 ; dx -= b
21: 89 56 f8                mov    WORD PTR [bp-0x8],dx  ; temp = dx = a - b
24: 8b 56 fe                mov    dx,WORD PTR [bp-0x2]  ; dx = a
27: 8b 46 fa                mov    ax,WORD PTR [bp-0x6]  ; ax = c
2a: 29 c2                   sub    dx,ax                 ; dx -= c (= a - c)
2c: 89 d1                   mov    cx,dx                 ; cx = dx = a - c
2e: 8b 46 f8                mov    ax,WORD PTR [bp-0x8]  ; ax = temp = a - b
31: 31 d2                   xor    dx,dx                 ; clear dx
33: f7 f1                   div    cx                    ; dx:ax /= cx (unsigned divide)
35: 89 c0                   mov    ax,ax                 ; (?) ax = ax
37: 89 c0                   mov    ax,ax                 ; (?) ax = ax
39: a3 00 00                mov    ds:0x0,ax             ; ds[0] = ax
3c: 90                      nop
3d: 89 c0                   mov    ax,ax                 ; (?) ax = ax
3f: 89 ec                   mov    sp,bp                 ; restore saved SP
41: 5d                      pop    bp                    ; pop saved frame pointer
42: 16                      push   ss  ;      ss
43: 1f                      pop    ds  ; ds =
44: c3                      ret

Отладка программы в GDB, эта инструкция вызывает segfault

movl   $0x46c70000,-0x2(%esi)

Это первые две команды перемещения для установки значений a и b, интерпретируемые с командой, декодированной в 32-битном режиме. Соответствующая разборка (без указания 16-битного режима) выглядит следующим образом:

$ objdump -M intel  -S test16.o > test16.s && cat test16.s

test16.o:     file format elf32-i386


Disassembly of section .text:

00000000 <test16>:
0:   55                      push   ebp
1:   89 e5                   mov    ebp,esp
3:   83 ec 08                sub    esp,0x8
6:   c7 46 fe 00 00 c7 46    mov    DWORD PTR [esi-0x2],0x46c70000
d:   fc                      cld    

Следующим шагом будет попытка найти способ перевести процессор в 16-битный режим.Это даже не должен быть реальный режим (поиск в Google в основном приводит к 16-битному реальному режиму x86), это может быть даже 16-битный защищенный режим.Но на данный момент использование эмулятора определенно кажется лучшим вариантом, и это больше для моего любопытства.Это все также характерно для x86.Для справки вот тот же файл, скомпилированный в 32-битном режиме, который имеет неявное продвижение до 32-битного со знаком int (от запуска gcc -m32 -c test16.c -o test16_32.o && objdump -M intel -S test16_32.o > test16_32.s):

test16_32.o:     file format elf32-i386


Disassembly of section .text:

00000000 <test16>:
0:  55                      push   ebp      ; save frame pointer
1:  89 e5                   mov    ebp,esp  ; copy SP to frame pointer
3:  83 ec 10                sub    esp,0x10 ; allocate 4 * 4bytes on stack
6:  66 c7 45 fa 00 00       mov    WORD PTR [ebp-0x6],0x0 ; uint16_t a = 0
c:  66 c7 45 fc 01 00       mov    WORD PTR [ebp-0x4],0x1 ; uint16_t b = 0
12: 0f b7 45 fa             movzx  eax,WORD PTR [ebp-0x6] ; eax = a
16: 83 e8 02                sub    eax,0x2                ; eax -= 2
19: 66 89 45 fe             mov    WORD PTR [ebp-0x2],ax  ; uint16_t c = (uint16_t) (a-2)
1d: 0f b7 55 fa             movzx  edx,WORD PTR [ebp-0x6] ; edx = a
21: 0f b7 45 fc             movzx  eax,WORD PTR [ebp-0x4] ; eax = b
25: 29 c2                   sub    edx,eax                ; edx -= b
27: 89 d0                   mov    eax,edx                ; eax = edx (= a - b)
29: 0f b7 4d fa             movzx  ecx,WORD PTR [ebp-0x6] ; ecx = a
2d: 0f b7 55 fe             movzx  edx,WORD PTR [ebp-0x2] ; edx = c
31: 29 d1                   sub    ecx,edx                ; ecx -= edx (= a - c)
33: 99                      cdq                           ; EDX:EAX = EAX sign extended (= a - b)
34: f7 f9                   idiv   ecx                    ; EDX:EAX /= ecx
36: 66 a3 00 00 00 00       mov    ds:0x0,ax              ; ds = (uint16_t) ax
3c: 90                      nop
3d: c9                      leave                         ; esp = ebp (restore stack pointer), pop ebp
3e: c3                      ret

Ответы [ 3 ]

19 голосов
/ 01 апреля 2019

Вы не можете, если не найдете какой-то особенный компилятор.Это сломало бы абсолютно все , включая ваш printf звонок.Генерация кода в 32-битном компиляторе может даже не быть способной генерировать 16-битный арифметический код, так как он обычно не требуется.

Рассматривали ли вы вместо этого использование эмулятора?

6 голосов
/ 01 апреля 2019

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

Если вы хотите запустить 16-битный код на 32-битнойВ системе, ваш наиболее вероятный шанс на успех - запустить его в chroot, который имеет сопоставимую среду выполнения, возможно, используя qemu-user-static, если вам тоже нужен перевод ISA.Тем не менее, я не уверен, что любая из платформ, поддерживаемых QEMU, имеет 16-битный ABI.

Это может написать собственный набор из 16-битных * 1010.* shim библиотеки, поддерживаемые собственными библиотеками вашей платформы - но я подозреваю, что это перевесит пользу для вас.

Обратите внимание, что для конкретного случая запуска 32-разрядных двоичных файлов x86 на 64-разрядныххост amd64, ядра Linux часто конфигурируются с поддержкой двух ABI (конечно, вам все еще нужны соответствующие 32-битные библиотеки).

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

Вы можете сделать сам код более осведомленным о размерах данных, которые он обрабатывает, например, выполнив:

printf("%hu\n", a - b);

Из документов fprintf:

h

Указывает, что следующий спецификатор преобразования d, i, o, u, x или X применяется к аргументу short int или unsigned short int (аргумент будет повышаться в соответствии с целочисленными переходами, ноего значение должно быть преобразовано в short int или unsigned short int перед печатью);

...