web-dev-qa-db-fra.com

Comment puis-je forcer la taille d'un int à des fins de débogage?

J'ai deux versions pour un logiciel que je développe, une pour un système embarqué où la taille d'un int est de 16 bits, et une autre pour tester sur le bureau où la taille d'un int est de 32 bits. J'utilise des types entiers à largeur fixe de <stdint.h>, mais les règles de promotion des nombres entiers dépendent toujours de la taille d'un entier.

Idéalement, je voudrais que quelque chose comme le code suivant s'imprime 65281 (promotion entière à 16 bits) au lieu de 4294967041 (promotion entière sur 32 bits) en raison de la promotion entière, de sorte qu'elle corresponde exactement au comportement sur le système embarqué. Je veux être sûr que le code qui donne une réponse lors des tests sur mon bureau donne exactement la même réponse sur le système embarqué. Une solution pour GCC ou Clang serait bien.

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

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

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

    return 0;
}

MODIFIER:

L'exemple que j'ai donné n'est peut-être pas le meilleur exemple, mais je veux vraiment que la promotion des entiers soit à 16 bits au lieu de 32 bits. Prenons l'exemple suivant:

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

La sortie est 0 sur un système 32 bits en raison de la troncature de la division entière après la promotion en int (signé), par opposition à 32767.

Jusqu'à présent, la meilleure réponse semble être d'utiliser un émulateur, ce qui n'est pas ce que j'espérais, mais je suppose que cela a du sens. Il semble qu'il devrait être théoriquement possible pour un compilateur de générer du code qui se comporte comme si la taille d'un entier était de 16 bits, mais je suppose qu'il ne devrait peut-être pas être trop surprenant qu'il n'y ait pas de moyen facile dans la pratique de le faire, et il n'y a probablement pas beaucoup de demande pour un tel mode et tout support d'exécution nécessaire.

EDIT 2:

C'est ce que j'ai exploré jusqu'à présent: il existe en fait une version de GCC qui cible l'i386 en mode 16 bits sur https://github.com/tkchia/gcc-ia16 . La sortie est un fichier COM COM, qui peut être exécuté dans DOSBox. Par exemple, les deux fichiers:

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

peut être compilé avec

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

produire a.com qui peut être exécuté dans DOSBox.

D:\>a
result: 32767

En examinant les choses un peu plus loin, ia16-elf-gcc produit en fait un elf 32 bits comme intermédiaire, bien que la sortie finale du lien par défaut soit un fichier 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

Je peux le forcer à créer un lien avec main.c compilé avec GCC standard, mais sans surprise, les segfaults exécutables résultants.

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

D'après un article ici , il semble qu'il devrait théoriquement être possible de lier la sortie du code 16 bits de ia16-elf-gcc au code 32 bits, bien que je ne sois pas vraiment sûr de savoir comment. Ensuite, il y a aussi le problème de l'exécution du code 16 bits sur un système d'exploitation 64 bits. Plus idéal serait un compilateur qui utilise toujours des registres et des instructions 32 bits/64 bits réguliers pour effectuer l'arithmétique, mais émule l'arithmétique via des appels de bibliothèque similaires à la façon dont, par exemple, un uint64_t est émulé sur un microcontrôleur (non 64 bits).

Le plus proche que j'ai pu trouver pour exécuter du code 16 bits sur x86-64 est ici , et cela semble expérimental/complètement non entretenu. À ce stade, le simple fait d'utiliser un émulateur semble être la meilleure solution, mais j'attendrai un peu plus longtemps pour voir si quelqu'un d'autre a des idées.

EDIT 3

Je vais aller de l'avant et accepter la réponse d'antti, bien que ce ne soit pas la réponse que j'espérais entendre. Si quelqu'un s'intéresse à la sortie de ia16-elf-gcc (je n'avais jamais entendu parler de ia16-elf-gcc auparavant), voici le démontage:

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

Notez que vous devez spécifier qu'il s'agit d'un code 16 bits, sinon objdump l'interprète comme du code 32 bits, qui correspond à différentes instructions (voir ci-dessous).

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

Débogage du programme dans GDB, cette instruction provoque le segfault

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

Ce sont les deux premières instructions de déplacement pour définir la valeur de a et b interprétées avec l'instruction décodée en mode 32 bits. Le démontage pertinent (ne spécifiant pas le mode 16 bits) est le suivant:

$ 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    

La prochaine étape serait d'essayer de trouver un moyen de mettre le processeur en mode 16 bits. Il n'est même pas nécessaire que ce soit le mode réel (les recherches Google produisent principalement des résultats pour le mode réel x86 16 bits), il peut même s'agir d'un mode protégé 16 bits. Mais à ce stade, l'utilisation d'un émulateur semble définitivement la meilleure option, et c'est plus pour ma curiosité. Tout cela est également spécifique à x86. Pour référence, voici le même fichier compilé en mode 32 bits, qui a une promotion implicite vers un int signé 32 bits (à partir de l'exécution de 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
22
JDW

Vous ne pouvez pas, sauf si vous trouvez un compilateur très spécial. Cela casserait absolument tout, y compris votre appel printf. La génération de code dans le compilateur 32 bits pourrait même ne pas être capable pour produire le code arithmétique 16 bits car il n'est généralement pas nécessaire.

Avez-vous envisagé d'utiliser un émulateur à la place?

19
Antti Haapala

Vous avez besoin d'un environnement d'exécution complet comprenant toutes les bibliothèques nécessaires pour partager l'ABI que vous implémentez.

Si vous souhaitez exécuter votre code 16 bits sur un système 32 bits, votre chance de succès la plus probable est de l'exécuter dans un chroot qui a un environnement d'exécution comparable, en utilisant éventuellement qemu-user-static si vous avez besoin de ISA également. Cela dit, je ne suis pas sûr que l'une des plates-formes prises en charge par QEMU ait un ABI 16 bits.

Il pourrait être possible d'écrire vous-même un jeu de cales 16 bits bibliothèques, soutenues par les bibliothèques natives de votre plate-forme - mais je soupçonne que l'effort serait supérieur à l'avantage pour vous.

Notez que dans le cas spécifique de l'exécution de binaires x86 32 bits sur un hôte AMD64 64 bits, les noyaux Linux sont souvent configurés avec une prise en charge double ABI (vous avez toujours besoin des bibliothèques 32 bits appropriées, bien sûr).

6
Toby Speight

Vous pouvez rendre le code lui-même plus conscient des tailles de données qu'il gère, en faisant par exemple:

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

À partir des documents de fprintf:

h

Spécifie qu'un spécificateur de conversion d, i, o, u, x ou X suivant s'applique à un argument int court ou int court non signé (l'argument aura été promu conformément aux promotions entières, mais sa valeur sera convertie en int court ou entier court non signé avant l'impression);

2
alk