私が開発しているソフトウェアのビルドが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
非常に特別なコンパイラを見つけない限り、できません。それはあなたのprintf
呼び出しを含む絶対にすべてを壊します。 32ビットコンパイラでのコード生成は、16ビット算術コードを生成するためにableでさえ必要ない場合があるため、通常は必要ありません。
代わりにエミュレータの使用を検討しましたか?
実装するABIを共有するために必要なすべてのライブラリを含めて、完全なランタイム環境が必要です。
32ビットシステムで16ビットコードを実行する場合、成功する可能性が最も高いのは、同等のランタイム環境があるchrootで実行することです。ISA変換も必要な場合は、qemu-user-static
を使用することもできます。 。とはいえ、QEMUでサポートされているプラットフォームのいずれかに16ビットABIがあるかどうかはわかりません。
それはmightプラットフォームのネイティブライブラリに支えられた16ビットのライブラリshimのライブラリを自分で書くことは可能ですが、その努力はあなたにとっての利益を上回ると思います。
64ビットAMD64ホストで32ビットx86バイナリを実行する特定のケースでは、Linuxカーネルは多くの場合、デュアルABIサポートで構成されます(もちろん、適切な32ビットライブラリが必要です)。
たとえば、次のようにすることで、コード自体が処理しているデータサイズをより意識させることができます。
printf("%hu\n", a - b);
fprintf
のドキュメントから:
h
次のd、i、o、u、x、またはX変換指定子がshort intまたはunsigned short int引数に適用されることを指定します(引数は整数の昇格に従って昇格されますが、その値はshort intに変換されます)または印刷前の符号なしshort int);