如何强制将int的大小用于调试目的?

时间:2019-04-01 08:28:50

标签: c gcc clang integer-promotion

对于正在开发的软件,我有两种版本,一种用于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;
}

编辑:

我给出的示例可能不是最好的示例,但是我确实希望整数提升为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;
}

在32位系统上的输出为0,因为与32767相对,提升为(有符号)int后从整数除法被截断了。

到目前为止,最好的答案似乎是使用仿真器,这不是我想要的,但是我认为确实是有道理的。从理论上来说,编译器似乎可以生成行为,就像int的大小为16位一样,但我想实践中没有简单的方法可能并不奇怪,对这种模式和任何必要的运行时支持的需求可能不大。

编辑2:

这是我到目前为止所探索的:实际上,有一个GCC版本以https://github.com/tkchia/gcc-ia16为目标,以16位模式的i386为目标。输出是一个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

再进一步研究一下,虽然默认情况下最终的链接输出是COM文件,但ia16-elf-gcc实际上确实会产生一个32位的elf作为中间变量。

$ 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

我可以强制它与使用常规GCC编译的main.c链接,但并不奇怪,它会产生可执行的段错误。

$ 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)

here帖子看来,从理论上讲应该可以将ia16-elf-gcc的16位代码输出链接到32位代码,尽管我实际上不确定如何做到。然后还有在64位OS上实际运行16位代码的问题。更理想的是,编译器仍然使用常规的32位/ 64位寄存器和指令执行算术运算,但是通过库调用来模拟算术运算,类似于在uint64_t上模拟$ objdump -M intel -mi386 -Maddr16,data16 -S test16.o > test16.s 64位)微控制器。

在x86-64上实际运行16位代码的地方,我能找到的最接近的是here,这似乎是实验性的/完全没有维护。在这一点上,仅使用模拟器似乎是最好的解决方案,但是我将等待一会儿,看看是否有人有任何想法。

编辑3

我将继续接受antti的答案,尽管这不是我希望听到的答案。如果有人对ia16-elf-gcc的输出是什么感兴趣(我以前从未听说过ia16-elf-gcc),那么这里是反汇编:

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

请注意,您必须指定它是16位代码,否则objdump会将其解释为32位代码,它映射到不同的指令(请参阅下一节)。

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

在GDB中调试程序,此指令会导致段错误

$ 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    

前两个移动指令用于设置用32位模式解码的指令解释的a和b的值。相关反汇编(未指定16位模式)如下:

gcc -m32 -c test16.c -o test16_32.o && objdump -M intel -S test16_32.o > test16_32.s

下一步将尝试找出一种将处理器置于16位模式的方法。它甚至不必是实模式(谷歌搜索通常会显示x86 16位实模式的结果),甚至可以是16位保护模式。但是在这一点上,使用仿真器绝对是最好的选择,而这更多是出于我的好奇心。所有这些也特定于x86。作为参考,以下是在32位模式下编译的同一文件,该文件隐式提升为32位带符号的int(通过运行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 ):

{{1}}

3 个答案:

答案 0 :(得分:19)

您不能,除非找到一些非常特殊的编译器。它将破坏所有内容,包括您的printf通话。 32位编译器中的代码生成甚至可能 无法生成16位算术代码,因为通常不需要。

您是否考虑过使用仿真器?

答案 1 :(得分:6)

您需要一个完整的运行时环境,其中包括所有必需的库以共享您正在实现的ABI。

如果要在32位系统上运行16位代码,则最有可能获得成功的机会是在具有类似运行时环境的chroot中运行它,如果需要,可以使用qemu-user-static ISA翻译也是如此。也就是说,我不确定QEMU支持的平台是否具有16位ABI。

有可能自己编写一套由平台的本地库支持的16位 shim 库-但我怀疑这样做的好处可能超过你。

请注意,对于在64位amd64主机上运行32位x86二进制文件的特定情况,Linux内核通常配置为具有双ABI支持(当然,您仍然需要适当的32位库)。 / p>

答案 2 :(得分:2)

例如,您可以使代码本身更了解其正在处理的数据大小:

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

摘自fprintf的文档:

  

h

     

指定后面的d,i,o,u,x或X转换说明符适用于short int或unsigned short int参数(该参数将根据整数提升进行提升,但应转换其值在打印前将short int或unsigned short int缩短);