Open Watcom IDE -- Cからとアセンブラールーチンを呼び出す ― 2018年03月02日 17:34

Open Watcom IDEをいじるに当たり、ただ漠といじってもつまらないので、 PC-DOS用のシェルの作成を計画しました。もちろんネタ本があります。 CQ出版社から1989年7月20日に出版された「Allen Holib著 横山和由訳 MS-DOS用Shellの実現 (On Command: Writing a Unix-Like Shell for MS-DOS)」です。 本棚の肥やしになっていたのを引っ張り出してきました。この本を購入した頃は、 まだまだ、コンパイラーなどのツールが高価で、実際の制作には、踏み切れませんでした。
内容は、丁寧にシェルの実装について、ソースリストを交えて解説してあります。 特に難しい内容ではなく、容易に著者の開発手順をたどり、シェルを作成することができそうです。 原著が書かれたのは、1986年頃で、その当時書き方で、MS-C Ver.4とMASMで記述されています。 現代のコーディングスタイルと比較して、古い書き方をしてあり、私の好みに合わない書き方です。 今回は、現代風のスタイルに合わせて書いていきます。主な変更点は、以下の通りです。
- 関数の宣言で、"int"型の変数を明示的に宣言していないのを 全て宣言するようにした。
- "unsinged int"型の宣言を "unsinged"としてあるのを、"unsinged int"と記述するようにした。
- register変数は、とりあえず使わない。
- ユーティリティーライブラリーは複数のソースファイルに分かれているが、共通の インクルードファイルにまとめてプロトタイプ宣言をしている。デバックしづらいので、一つの Cソースファイルに対応して一つのヘッダーファイルを作成しプロトタイプ宣言をするようにした。
- 標準関数のプロトタイプ宣言を 直接コーディングしてあるのを ヘッダファイルをインクルード するようにした。
- Static属性の付いた関数は、そのままではデバッグしづらいので、コンパイル時に"DEBUG"を定義してないときは、 static属性をつけてコンパイルをするように"#ifndef DEBUG"を関数の宣言に追加した。
- 制作にはOpen Watcom C/C++コンパイラーとWASMを使用する。
- 開発はOS/2で行い、デバッグはOS/2のPC-DOS窓で行う。
開発はC:ドライブに"Shell"ディレクトリを作成し、その中にコードを書くこととしました。
前準備ができたので、まずは、手始めに、Cのmain()関数から、 アセンブラーで記述した関数foo()を呼び出す練習をしました。 ソースコードは、下記の通りです。
/* callasm.c */ #include#include "foo.h" int main() { int p; p = 7; printf("%d => %d\n",p,foo(p)); return 0; }
/* foo.h */ extern int foo(int);
; foo.asm _text SEGMENT BYTE PUBLIC 'CODE' _text ENDS _data SEGMENT WORD PUBLIC 'DATA' _data ENDS const SEGMENT WORD PUBLIC 'CONST' const ENDS _bss SEGMENT WORD PUBLIC 'BSS' _bss ENDS dgroup GROUP const,_bss,_DATA ASSUME CS:_text, DS:dgroup, SS:dgroup, ES:dgroup _text SEGMENT ; --------------------------------------------------------------------- ; foo(int val) ; val=bp+4 _foo PROC NEAR PUBLIC _foo push bp mov bp,sp mov ax,[bp+4] shl ax,1 mov sp,bp pop bp ret _foo ENDP _TEXT ENDS END
Wasmは、MASM互換といわれているので、一発でうまくいくと思っていたのですが、だめでした。 ビルドしてみると、下記のようにリンケージでエラーが出ます。callasm.cで呼び出している関数foo()は、 コンパイルされると、"foo_"となるようです。
cd C:\SHELL\SAMPLE wmake -f C:\SHELL\SAMPLE\callasm.mk -h -e C:\SHELL\SAMPLE\callasm.exe wasm FOO.ASM -i="E:\WATCOM/h" -ms -fp0 -w4 -e25 Open Watcom Assembler Version 1.9 Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved. Source code is available under the Sybase Open Watcom Public License. See http://www.openwatcom.org/ for details. FOO.ASM: 27 lines, 0 warnings, 0 errors wcc callasm.c -i="E:\WATCOM/h" -w4 -e25 -zp2 -od -fp2 -fpi87 -bt=dos -fo=.obj -ms Open Watcom C16 Optimizing Compiler Version 1.9 Portions Copyright (c) 1984-2002 Sybase, Inc. All Rights Reserved. Source code is available under the Sybase Open Watcom Public License. See http://www.openwatcom.org/ for details. callasm.c: 11 lines, included 773, 0 warnings, 0 errors Code size: 60 wlink name callasm d all sys dos op m op maxe=25 op q op symf @callasm.lk1 Error! E2028: foo_ is an undefined reference file callasm.obj(C:\SHELL\SAMPLE\callasm.c): undefined symbol foo_ Error(E42): Last command making (C:\SHELL\SAMPLE\callasm.exe) returned a bad status Error(E02): Make execution terminated Execution completeというわけで、foo.asm内の"_foo"を"foo_"に編集し再ビルドしました。
; foo.asm _text SEGMENT BYTE PUBLIC 'CODE' _text ENDS _data SEGMENT WORD PUBLIC 'DATA' _data ENDS const SEGMENT WORD PUBLIC 'CONST' const ENDS _bss SEGMENT WORD PUBLIC 'BSS' _bss ENDS dgroup GROUP const,_bss,_DATA ASSUME CS:_text, DS:dgroup, SS:dgroup, ES:dgroup _text SEGMENT ; --------------------------------------------------------------------- ; foo(int val) ; val=bp+4 foo_ PROC NEAR PUBLIC foo_ push bp mov bp,sp mov ax,[bp+4] shl ax,1 mov sp,bp pop bp ret foo_ ENDP _TEXT ENDS END
再ビルドしてみると、エラーはなくなりました。
cd C:\SHELL\SAMPLE wmake -f C:\SHELL\SAMPLE\callasm.mk -h -e C:\SHELL\SAMPLE\callasm.exe wasm FOO.ASM -i="E:\WATCOM/h" -ms -fp0 -w4 -e25 Open Watcom Assembler Version 1.9 Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved. Source code is available under the Sybase Open Watcom Public License. See http://www.openwatcom.org/ for details. FOO.ASM: 27 lines, 0 warnings, 0 errors wlink name callasm d all sys dos op m op maxe=25 op q op symf @callasm.lk1 Execution complete
テストです。
C:\SHELL\SAMPLE>callasm 7 => 3522
アセンブラールーチン"foo"は、受け取った引数を左に1bitシフトする事で受け取った引数を2倍して値を返します。 結果は、"14"になるはずですが、そうなっていません。確認のため、 "callasm.c"のコンパイル結果を見るためにコンパイルリストをコンパイラーに吐かせようとしましたが、"wcc"のオプションに 該当するものが見当たらいません。調べてみると"Open Watcom Assembler"Wikiの"Disassembler"の項目に、 以下の記述を見つけました。
The assembler does not have listing facilities; instead the use of wdis for generating listings is recommended.
この指示に従い、callasm.objを"wdis"で、逆アセンブルしてみました。
Module: C:\SHELL\SAMPLE\callasm.c GROUP: 'DGROUP' CONST,CONST2,_DATA Segment: _TEXT BYTE USE16 0000003C bytes 0000 main_: 0000 B8 18 00 mov ax,0x0018 0003 E8 00 00 call __STK 0006 53 push bx 0007 51 push cx 0008 52 push dx 0009 56 push si 000A 57 push di 000B 55 push bp 000C 89 E5 mov bp,sp 000E 81 EC 04 00 sub sp,0x0004 0012 C7 46 FE 07 00 mov word ptr -0x2[bp],0x0007 0017 8B 46 FE mov ax,word ptr -0x2[bp] 001A E8 00 00 call foo_ 001D 50 push ax 001E FF 76 FE push word ptr -0x2[bp] 0021 B8 00 00 mov ax,offset DGROUP:L$1 0024 50 push ax 0025 E8 00 00 call printf_ 0028 83 C4 06 add sp,0x0006 002B C7 46 FC 00 00 mov word ptr -0x4[bp],0x0000 0030 8B 46 FC mov ax,word ptr -0x4[bp] 0033 89 EC mov sp,bp 0035 5D pop bp 0036 5F pop di 0037 5E pop si 0038 5A pop dx 0039 59 pop cx 003A 5B pop bx 003B C3 ret Routine Size: 60 bytes, Routine Base: _TEXT + 0000 No disassembly errors Segment: CONST WORD USE16 0000000A bytes 0000 L$1: 0000 25 64 20 3D 3E 20 25 64 0A 00 %d => %d.. Segment: CONST2 WORD USE16 00000000 bytes Segment: _DATA WORD USE16 00000000 bytes
これを見ると、"0017"行目から"001A"行目で、"ax"レジスタに引数の値を入れて、 "foo_"を呼び出しているのが分かります。想定外でした。関数の引数は、基本スタックに 積む物だと思っていたのですが、Open Watcom C/C++では違うようです。困りました。アセンブラールーチンを 書き換えたくはありませんので、コンパイラーのオプションスイッチで、切り抜ける方法を探ってみたところ、 該当しそうなオプションスイッチがありました。
-ecc set default calling convention to __cdecl -ecd set default calling convention to __stdcall -ecf set default calling convention to __fastcall -ecp set default calling convention to __pascal -ecr set default calling convention to __fortran -ecs set default calling convention to __syscall -ecw set default calling convention to __watcall (default)
OS/2のシステム関数を呼び出すときには、関数に"CDECL"とつけていたように記憶しています。"-ecc"が 怪しいです。まずは、foo.asm中の"foo_"を"_foo"に戻し、"-ecc"をつけて再ビルドしてみました。
cd C:\SHELL\SAMPLE wmake -f C:\SHELL\SAMPLE\callasm.mk -h -e C:\SHELL\SAMPLE\callasm.exe wasm FOO.ASM -i="E:\WATCOM/h" -ms -fp0 -w4 -e25 Open Watcom Assembler Version 1.9 Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved. Source code is available under the Sybase Open Watcom Public License. See http://www.openwatcom.org/ for details. FOO.ASM: 27 lines, 0 warnings, 0 errors wcc callasm.c -i="E:\WATCOM/h" -w4 -e25 -zp2 -ecc -od -fp2 -fpi87 -bt=dos -fo=.obj -ms Open Watcom C16 Optimizing Compiler Version 1.9 Portions Copyright (c) 1984-2002 Sybase, Inc. All Rights Reserved. Source code is available under the Sybase Open Watcom Public License. See http://www.openwatcom.org/ for details. callasm.c: 11 lines, included 773, 0 warnings, 0 errors Code size: 63 wlink name callasm d all sys dos op m op maxe=25 op q op symf @callasm.lk1 Execution complete
callasm.objを逆アセンブルした結果は、以下のようになり、関数のパラメーターはスタックに積んで 渡すようになりました。
Module: C:\SHELL\SAMPLE\callasm.c GROUP: 'DGROUP' CONST,CONST2,_DATA Segment: _TEXT BYTE USE16 0000003F bytes 0000 main_: 0000 B8 18 00 mov ax,0x0018 0003 E8 00 00 call __STK 0006 53 push bx 0007 51 push cx 0008 52 push dx 0009 56 push si 000A 57 push di 000B 55 push bp 000C 89 E5 mov bp,sp 000E 81 EC 04 00 sub sp,0x0004 0012 C7 46 FE 07 00 mov word ptr -0x2[bp],0x0007 0017 FF 76 FE push word ptr -0x2[bp] 001A E8 00 00 call _foo 001D 83 C4 02 add sp,0x0002 0020 50 push ax 0021 FF 76 FE push word ptr -0x2[bp] 0024 B8 00 00 mov ax,offset DGROUP:L$1 0027 50 push ax 0028 E8 00 00 call printf_ 002B 83 C4 06 add sp,0x0006 002E C7 46 FC 00 00 mov word ptr -0x4[bp],0x0000 0033 8B 46 FC mov ax,word ptr -0x4[bp] 0036 89 EC mov sp,bp 0038 5D pop bp 0039 5F pop di 003A 5E pop si 003B 5A pop dx 003C 59 pop cx 003D 5B pop bx 003E C3 ret Routine Size: 63 bytes, Routine Base: _TEXT + 0000 No disassembly errors Segment: CONST WORD USE16 0000000A bytes 0000 L$1: 0000 25 64 20 3D 3E 20 25 64 0A 00 %d => %d.. Segment: CONST2 WORD USE16 00000000 bytes Segment: _DATA WORD USE16 00000000 bytes
テストです。
C:\SHELL\SAMPLE>callasm 7 => 14
成功です。これで、Cからアセンブラールーチンを呼び出す手順を確認できました。一山、越えました。
IDEその後 ― 2018年03月26日 15:58
Open WATCOM C/C++のIDEを使って、前回説明したルールに沿って PC-DOS SHELLの製作を続けています。
幾つかサンプルを示します。
今回、製作するプロジェクト名は、"SHELL"とします。作業デイレクトリーは"C:\SHELL"です。 また、プロジェクトのターゲットは、3つです。完成品"SH.EXE"、デバッグ用ターゲット"DEBMAIN.EXE"。 いずれも、DOS 16ビットの実行モジュールです。 この外に、テキストでは、ライブラリー"SHELLIB.LIB"を作りますので、 これもターゲットとします。
さて、さきに決めたコーデイングルールに従い、プリグラムを書いていきます。幾つかサンプルを示します。
関数の頭書きの例です。オリジナルはこのようになっています。
/* cpy.c */ char *cpy(dest,src) register char *dest,*src; { : (中略) : return dest; }これ下記のようにすると同時にヘッダーファイルを追加します。
/* cpy.c */ char *cpy(char *dest,char *src) { : (中略) : return dest; }
ヘッダーファイルは以下のとおりです。これを必要に応じてインクルードします。
/* cpy.h */ extern char *cpy(char*,char*);
型を省略された引数は、きちんと宣言します。例えば、
/* next.c */ char *next(**linep,delim,esc) { char **linep; { : (中略) : return start; }
これは、このようにします。もちろん、ヘッダーファイルも同様です。
/* next.c */ char *next(char **linep,char delim,char esc) { { : (中略) : return start; }
/* next.h */ extern char *next(char **,char,char);
標準関数の定義は、本書では以下のようにしていますが、 ヘッダーファイルをインクルードするようにします。下記の例には、strlen()の宣言に誤植があります。 引数が一つなのにカンマが付いています。おそらく、版下を作成するのに、ソースファイルを 手書きで入力したのでしょう。現代ならば、インクルードして組み込むでしょうね。
/* strsave.c */ extern int strcpy (char*, char*); extern char *malloc(unsigned ); extern int strlen (char*, ); char *strsave(str) char *str; { : (中略) : return NULL; }
これは、下記のように記述しました。
/* strsave.c */ #include#include char *strsave(char *str) { : (中略) : return NULL; }
static宣言された関数は、そのままでは、別のコンパイル単位からは見えなくなります。 これでは、デバッグをしずらいので、コンパイル時に文字列"DEBUG"が定義されている時は、 static宣言をはずすようにします。例えば下記の場合は、
/* reargv.c */ : (中略) : extern unsigned char _osmajor; static int Envlen; : (中略) : /* --------------------------------------------------------------------- */ static char *nextarg(pp) char **pp; { : (中略) : return start; } /* --------------------------------------------------------------------- */ int reargv(argcp,argvp) char *(**argvp); int *( argcp); { : (中略) : return 1; } /* --------------------------------------------------------------------- */ int envlen(void) { : (中略) : return Envlen; }このようにします。
/* reargv.c */ : (中略) : extern unsigned char _osmajor; static int Envlen; : (中略) : /* --------------------------------------------------------------------- */ #ifndef DEBUG static #endif char *nextarg(char **pp) { : (中略) : return start; } /* --------------------------------------------------------------------- */ int reargv(int *argcp,char *(**argvp)) { : (中略) : return 1; } /* --------------------------------------------------------------------- */ int envlen(void) { : (中略) : return Envlen; }
リスト中、MS-C依存の変数"_osmajor"があります。幸いなことに、Open WATCOM C/C++でも、 サポートされています。
"DEBUG"の宣言は、IDEの"C Compiler Switches"の "Macro Definitions: [-d]"欄に"DEBUG"と書きコンパイルします。デバッグが終わったら、 "DEBUG"を取り去り、再コンパイルしときます。
MS-Cに依存している個所は、もう一つありました。アセンブラーで一部書かれています。 その中で、MS-C独自の内部関数"__chkstk"関数を呼び出しています。これは、 実行時にスタックオーバーフローの検出をするルーチンです。Open WATCOM C/C++にはありません。 "__STK"が、同等の機能を果たすものと思われますが、仕様が公開されていません。今回は、 "__chkstk"を無視する事とします。
このテキスト中、"isdir.c"は、コードの最後の数行が欠落しています。編集ミスだと思います。 しかし、コードのコメントを読めば欠落している部分を補うことができます。 この外にも、バグを見つけました。それは、次回に報告します。
最近のコメント