Search code examples
cgccclanginteger-promotion

How can I force the size of an int for debugging purposes?


I have two builds for a piece of software I'm developing, one for an embedded system where the size of an int is 16 bits, and another for testing on the desktop where the size of an int is 32 bits. I am using fixed width integer types from <stdint.h>, but integer promotion rules still depend on the size of an int.

Ideally I would like something like the following code to print 65281 (integer promotion to 16 bits) instead of 4294967041 (integer promotion to 32 bits) because of integer promotion, so that it exactly matches the behavior on the embedded system. I want to be sure that code which gives one answer during testing on my desktop gives the exact same answer on the embedded system. A solution for either GCC or Clang would be fine.

#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:

The example I gave might not have been the best example, but I really do want integer promotion to be to 16 bits instead of 32 bits. Take the following example:

#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;
}

The output is 0 on a 32-bit system because of truncation from integer division after promotion to a (signed) int, as opposed to 32767.

The best answer so far seem to be to use an emulator, which is not what I was hoping for, but I guess does make sense. It does seem like it should be theoretically possible for a compiler to generate code that behaves as if the size of an int were 16 bits, but I guess it maybe shouldn't be too surprising that there's no easy way in practice to do this, and there's probably not much demand for such a mode and any necessary runtime support.

EDIT 2:

This is what I've explored so far: there is in fact a version of GCC which targets the i386 in 16-bit mode at https://github.com/tkchia/gcc-ia16. The output is a DOS COM file, which can be run in DOSBox. For instance, the two files:

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;
}

can be compiled with

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

to produce a.com which can be run in DOSBox.

D:\>a
result: 32767

Looking into things a little further, ia16-elf-gcc does in fact produce a 32-bit elf as an intermediate, although the final link output by default is a COM file:

$ 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

I can force it to link with main.c compiled with regular GCC, but not surprisingly, the resulting executable 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)

From a post here, it seems like it should theoretically be possible to link the 16-bit code output from ia16-elf-gcc to 32-bit code, although I'm not actually sure how. Then there is also the issue of actually running 16-bit code on a 64-bit OS. More ideal would be a compiler that still uses regular 32-bit/64-bit registers and instructions for performing the arithmetic, but emulates the arithmetic through library calls similar to how for instance a uint64_t is emulated on a (non-64-bit) microcontroller.

The closest I could find for actually running 16-bit code on x86-64 is here, and that seems experimental/completely unmaintained. At this point, just using an emulator is starting to seem like the best solution, but I will wait a little longer and see if anyone else has any ideas.

EDIT 3

I'm going to go ahead and accept antti's answer, although it's not the answer I was hoping to hear. If anyone is interested in what the output of ia16-elf-gcc is (I'd never even heard of ia16-elf-gcc before), here is the disassembly:

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

Notice that you must specify that it is 16 bit code, otherwise objdump interprets it as 32-bit code, which maps to different instructions (see further down).

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

Debugging the program in GDB, this instruction causes the segfault

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

Which is the first two move instructions for setting the value of a and b interpreted with the instruction decoded in 32-bit mode. The relevant disassembly (not specifying 16-bit mode) is as follows:

$ 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    

The next step would be trying to figure out a way to put the processor into 16-bit mode. It doesn't even have to be real mode (google searches mostly turn up results for x86 16-bit real mode), it can even be 16-bit protected mode. But at this point, using an emulator definitely seems like the best option, and this is more for my curiosity. This is all also specific to x86. For reference here's the same file compiled in 32-bit mode, which has an implicit promotion to a 32-bit signed int (from running 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

Solution

  • You can't, unless you find some very special compiler. It would break absolutely everything, including your printf call. The code generation in the 32-bit compiler might not even be able to produce the 16-bit arithmetic code as it is not commonly needed.

    Have you considered using an emulator instead?