web-dev-qa-db-ja.com

デバッグ目的でintのサイズを強制するにはどうすればよいですか?

私が開発しているソフトウェアのビルドが2つあります。1つは組み込みシステム用で、intのサイズは16ビットで、もう1つはデスクトップでテストし、intのサイズは32ビットです。 <stdint.h>の固定幅整数型を使用していますが、整数の昇格ルールは引き続きintのサイズに依存します。

理想的には、次のコードのように、整数昇格のために65281(32ビットへの整数昇格)ではなく4294967041(16ビットへの整数昇格)を印刷して、組み込みシステム。デスクトップでのテスト中に1つの答えを与えるコードが、組み込みシステムでもまったく同じ答えを与えることを確認したいと思います。 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;
}

編集:

私が示した例は最良の例ではなかったかもしれませんが、整数の昇格を32ビットではなく16ビットにしたいのです。次の例を見てください。

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

これは私がこれまで調べてきたものです。実際には、i-386を16ビットモードでターゲットにするGCCのバージョンが https://github.com/tkchia/gcc-ia16 にあります。出力はDOS COMファイルであり、DOSBoxで実行できます。たとえば、2つのファイル:

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

dOSBoxで実行できるa.comを生成します。

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ビットOSで実際に16ビットコードを実行する問題もあります。より理想的なのは、通常の32ビット/ 64ビットのレジスターと演算を実行するための命令を引き続き使用するコンパイラーですが、たとえば、uint64_tが(非64ビット)マイクロコントローラー。

X86-64で実際に16ビットコードを実行するために私が見つけることができる最も近いものは here であり、実験的/完全にメンテナンスされていないようです。この時点で、エミュレーターを使用するだけが最良の解決策のように見え始めていますが、もう少し待って、他の誰かがアイデアを持っているかどうかを確認します。

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

これは、32ビットモードでデコードされた命令で解釈されるaおよびbの値を設定するための最初の2つのmove命令です。関連する逆アセンブリ(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ビットモードでコンパイルされた同じファイルを以下に示します。これには、(gcc -m32 -c test16.c -o test16_32.o && objdump -M intel -S test16_32.o > test16_32.sの実行により)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
22
JDW

非常に特別なコンパイラを見つけない限り、できません。それはあなたのprintf呼び出しを含む絶対にすべてを壊します。 32ビットコンパイラでのコード生成は、16ビット算術コードを生成するためにableでさえ必要ない場合があるため、通常は必要ありません。

代わりにエミュレータの使用を検討しましたか?

19
Antti Haapala

実装するABIを共有するために必要なすべてのライブラリを含めて、完全なランタイム環境が必要です。

32ビットシステムで16ビットコードを実行する場合、成功する可能性が最も高いのは、同等のランタイム環境があるchrootで実行することです。ISA変換も必要な場合は、qemu-user-staticを使用することもできます。 。とはいえ、QEMUでサポートされているプラ​​ットフォームのいずれかに16ビットABIがあるかどうかはわかりません。

それはmightプラットフォームのネイティブライブラリに支えられた16ビットのライブラリshimのライブラリを自分で書くことは可能ですが、その努力はあなたにとっての利益を上回ると思います。

64ビットAMD64ホストで32ビットx86バイナリを実行する特定のケースでは、Linuxカーネルは多くの場合、デュアルABIサポートで構成されます(もちろん、適切な32ビットライブラリが必要です)。

6
Toby Speight

たとえば、次のようにすることで、コード自体が処理しているデータサイズをより意識させることができます。

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

fprintfのドキュメントから:

h

次のd、i、o、u、x、またはX変換指定子がshort intまたはunsigned short int引数に適用されることを指定します(引数は整数の昇格に従って昇格されますが、その値はshort intに変換されます)または印刷前の符号なしshort int);

2
alk