【问题标题】:How can I force the size of an int for debugging purposes?如何强制 int 的大小进行调试?
【发布时间】:2019-08-22 08:50:24
【问题描述】:

我正在开发的软件有两个版本,一个用于 int 大小为 16 位的嵌入式系统,另一个用于在 int 大小为 32 位的桌面上进行测试。我正在使用来自<stdint.h> 的固定宽度整数类型,但整数提升规则仍然取决于 int 的大小。

理想情况下,由于整数提升,我希望像下面的代码打印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,因为在提升为(有符号)int 后从整数除法截断,而不是 32767

到目前为止,最好的答案似乎是使用模拟器,这不是我所希望的,但我想确实有道理。从理论上讲,编译器似乎应该有可能生成表现得好像 int 的大小是 16 位的代码,但我想在实践中没有简单的方法来做到这一点,这可能并不令人惊讶,并且对这种模式和任何必要的运行时支持的需求可能并不多。

编辑 2:

这是我目前所探索的:事实上,有一个 GCC 版本以 16 位模式在 i386 为目标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);
}

ma​​in.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 位 elf 作为中间体,尽管默认情况下最终的链接输出是一个 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

我可以强制它与使用常规 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 位操作系统上实际运行 16 位代码的问题。更理想的是一个编译器,它仍然使用常规的 32 位/64 位寄存器和指令来执行算术,但通过库调用来模拟算术,类似于在(非 64-位)微控制器。

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

编辑 3

我将继续接受 antti 的回答,尽管这不是我希望听到的答案。如果有人对 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 位模式的方法。它甚至不必是实模式(谷歌搜索大多会找到 x86 16 位实模式的结果),它甚至可以是 16 位保护模式。但在这一点上,使用模拟器绝对是最好的选择,这更多是出于我的好奇心。这也是 x86 特有的。作为参考,这里是在 32 位模式下编译的同一个文件,它隐式提升为 32 位带符号的 int(从运行 gcc -m32 -c test16.c -o test16_32.o &amp;&amp; objdump -M intel -S test16_32.o &gt; 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

【问题讨论】:

  • 你不能,因为底层 ABI 期望你的程序有一个指定的对齐方式。为此,您必须重新编译整个系统。 that it exactly matches the behavior on the embedded system 所以如果你想便携,不要使用inta-b 表达式的类型是 int - 将其转换为 uint8_t 并使用 PRNu8 打印它。如果您使用longint 类型,您的程序将永远无法移植。该行应该是printf("% "PRNu8 "\n", (uint8_t)(a - b)); - 这样它将在任何架构任何系统上打印1
  • 为什么不使用强制转换为 uint16? printf("%u\n", (uint16_t)(a - b));
  • 您可以在桌面上尝试 c++ 版本。使用 Int16 类,您可以重载算术运算符。
  • 投到uint16_t 的好处只有这么多......
  • @PawełDymowski 不,使用%u 打印已签名的 int 仍然是 UB。因为有些平台的无符号值可能有陷阱位。 Which integral promotions do take place when printing a char?, What happens when I use the wrong format specifier?

标签: c gcc clang integer-promotion


【解决方案1】:

你不能,除非你找到一些非常特殊的编译器。它会破坏一切,包括你的printf 电话。 32 位编译器中的代码生成甚至可能能够生成 16 位算术代码,因为它并不常用。

您是否考虑过使用模拟器?

【讨论】:

    【解决方案2】:

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

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

    可能可以为自己编写一组 16 位 shim 库,由您平台的本地库支持 - 但我怀疑付出的努力会超过你。

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

    【讨论】:

      【解决方案3】:

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

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

      来自fprintf 的文档:

      h

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

      【讨论】:

      • 但实际上除了问题之外,如果目标上的 uint16_t 类型定义为 int%hu 将不正确(尽管表示基本相同)。跨度>
      • @AnttiHaapala:为什么int 不正确?
      • hmm... 使用PRI16u 怎么样?:D
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-03
      • 1970-01-01
      • 2015-04-15
      • 1970-01-01
      • 2015-07-18
      • 1970-01-01
      • 2011-05-21
      相关资源
      最近更新 更多