beroの日記: DreamcastでOS作った#kernelvm 補講
* OpenGL
PowerVR、というか当時のGPUはOpenGLの実装に必要なHW機能はだいたいある
(イマドキの最新GPUはGP(多目的)GPU上にレンダリングパイプラインをソフト実装するのかもしれんが)
ので例えばこんなstraightfowardな変換で済む(場合もある)。
libgl-2002-10-04.zip
libgl/gllight.c
/* Lighting */
void glShadeModel(GLenum mode) {
CHECK_BEGIN();
switch(mode)
{
case GL_FLAT:
gl_poly_cxt.gen.shading = PVR_SHADE_FLAT;
break;
case GL_SMOOTH:
gl_poly_cxt.gen.shading = PVR_SHADE_GOURAUD;
break;
default:
CHECK_ENUM(0,mode);
}
}
ソフトウェアレンダリングのほうが作業量的には大変。ただしHW叩くのは仕様不明とかDMAとの関連で固まるとかデバッグ大変とか諸刃の剣。
頂点変換はインラインアセンブラでSH-4専用命令でやってる
libgl/gldraw.c
#define mat_rot_single4(x, y, z, w) { \
register float __x __asm__("fr0") = (x); \
register float __y __asm__("fr1") = (y); \
register float __z __asm__("fr2") = (z); \
register float __w __asm__("fr3") = (w); \
__asm__ __volatile__( \
"ftrv xmtrx,fv0\n" \
: "=f" (__x), "=f" (__y), "=f" (__z),"=f" (__w) \
: "0" (__x), "1" (__y), "2" (__z), "3" (__w) \
); \
x = __x; y = __y; z = __z; w = __w; \
}
Quake - QuakeII(ディスプレイリストかなんか追加) - HexenII(重ね合わせモードかなんか追加) の順に必要な部分のみ実装していったのでフル実装ではないが、
このへんで後継が出てる模様。
http://www.dreamcast-scene.com/index.php/Main/KGL-X
* plugin dll
elfは一言でいうとなんでもぶち込める(*)コンテナなので、フル実装は大変かもしれないが、minimalな実装は簡単。
(*) 例えばglibc 2.5以降だっけ?は独自ハッシュテーブルぶち込んでシンボル検索を高速化している
例えば静的リンクされた実行ファイルのロードなら、ヘッダからロードアドレス、(デバッグ情報等を除く実質の)ファイルサイズ、エントリアドレスを取得して、本体をロードアドレスにロードしてエントリに飛ぶだけでよい。
(MMU対応OSで)もうすこし真面目にするなら、セクションヘッダからセクション情報を読み、上記の「本体」部分をセクション毎に実行可能/Read可能/Write可能とか設定する。
俺OSではdllの実装に静的オブジェクト(.o)をそのまま使っている。
普通、elfの共有ライブラリは、複数のプロセスから「共有」できるようにpicだったりGOTを駆使してたりするが、
拡張pluginとして一つのプロセスからのみ使うことを想定するなら、「共有」の機能は不要。
なので静的オブジェクト(.o)をそのまま使う。
静的オブジェクトは通常、実行ファイル作成時に静的リンカがアドレスを決定して一つの静的実行ファイルにするが、この静的リンカの作業を実行時に行う感じ。
linuxのカーネルモジュールも多分こんな感じじゃやないかと(読んでないけど)。
静的オブジェクト、つまり.o ファイルは
- プログラムコード本体(通常0番地から開始)
- 0番地以外に配置した時に変更が必要な部分の再配置データ
- シンボルデータ
が含まれている。
これを再配置するには
- openしてread
- obj内に無いシンボルのアドレスを解決
- アドレス書き換えが必要な部分を書き換え
dcquake2-2002-08-10.tar.gz
quake2-3.21/dc/dc/elfload.c (約400行)
このへんでヘッダ読んでる
98-109
fd = open(name,O_RDONLY|O_BINARY);
if (fd == -1) return NULL;read(fd,hdr,sizeof(hdr[0]));
if (memcmp(hdr->ident, ident, sizeof(ident))!=0 || hdr->machine!=EM_SH) {
err("invalid header");
}shdrs = malloc_read(fd,hdr->shoff,hdr->shnum*sizeof(*shdrs));
shstrtab = malloc_read(fd,shdrs[hdr->shstrndx].offset, shdrs[hdr->shstrndx].size);
このへんで未定義シンボル(実行ファイル側にある)のアドレスを解決。
189-220
for(i=0;i<n_symtab;i++) {
switch(symtab[i].shndx) {
case UND: {
void *value;
char *name = strtab + symtab[i].name;
if (name[0]==0) break;
value = find_global(name);
if (value==NULL) {
err("unresolve %s\n",name);
}
symtab[i].value = (uint32)value;
// printf("UND:%p %s\n",symtab[i].value,strtab + symtab[i].name);
break;
}
case ABS:
break;
case COM:
/* i thinks com.value means align (always 4) */
com = (com+symtab[i].value -1)&~(symtab[i].value-1);
if (align<symtab[i].value) align = symtab[i].value;
symtab[i].value = com;
com+=symtab[i].size;
#ifdef DEBUG
printf("COM:%p %d %s\n",symtab[i].value,symtab[i].size,strtab + symtab[i].name);
#endif
default:
if ((symtab[i].info & 0xf0)==STB_GLOBAL) {
n_syms++;
}
break;
}
}
find_globalがどうなってるか見ると、
- nmで.oから未定義シンボルを抽出
- perlスクリプトで整形して、あらかじめ必要なグローバルシンボルの表を作っておく
struct sym_t {
char *name;
void *addr;
};extern memcmp();
.
.
.struct sym_t symbols[]={
{"_memcmp",&memcmp},
.
.
}
みたいなテーブル作っといて、それを引いてるっぽい。
このへんで本体読んでる
R/W/Xとかの区別はしていない。ただしbss(0の部分)はロードする代わりにmemsetで埋めてる
226-240
/* load code */
imgbase = memalign(com,align);
for(i=0;i<hdr->shnum;i++) {
if (shdrs[i].flags & SHF_ALLOC) {
shdrs[i].addr += (uint32)imgbase;
switch(shdrs[i].type) {
case SHT_NOBITS: /* bss */
memset((void*)shdrs[i].addr, 0, shdrs[i].size);
break;
default:
lseek(fd,shdrs[i].offset,SEEK_SET);
read(fd,(void*)shdrs[i].addr, shdrs[i].size);
break;
}
printf("%p %s\n",shdrs[i].addr,shstrtab + shdrs[i].name);
}
}
このへんで再配置
269-317
/* relocation */
for(i=0;i<hdr->shnum;i++) {
int j,n_reltab;
uint32 sectbase;if (shdrs[i].type!=SHT_RELA || !(shdrs[shdrs[i].info].flags&SHF_ALLOC)) continue;
sectbase = shdrs[shdrs[i].info].addr;
#ifdef DEBUG
printf("%s -> %s(%p)\n",shstrtab + shdrs[i].name, shstrtab + shdrs[shdrs[i].info].name,sectbase);
#endif
reltab = malloc_read(fd,shdrs[i].offset,shdrs[i].size);
n_reltab = shdrs[i].size / sizeof(struct elf_rela_t);for(j=0;j<n_reltab;j++) {
int sym;
uint32 value;
if (ELF32_R_TYPE(reltab[j].info) != R_SH_DIR32) {
err("unsupport rela type %02x\n",ELF32_R_TYPE(reltab[j].info));
}
sym = ELF32_R_SYM(reltab[j].info);
switch(symtab[sym].info&0xf) {
case STT_OBJECT:
case STT_FUNC:
case STT_SECTION:
break;
case STN_UNDEF:
if (symtab[sym].shndx==UND) break;
default:
err("unsupport sym %x\n",symtab[sym].info);
}
value = *(uint32*)(sectbase + reltab[j].offset);
#ifdef DEBUG
printf("%p %x %x -> %x (%s+%d)\n",
sectbase + reltab[j].offset,
reltab[j].addend,
value,
value + symtab[sym].value + reltab[j].addend,
((symtab[sym].info&0xf)==STT_SECTION)?(shstrtab + shdrs[symtab[sym].shndx].name) : (strtab + symtab[sym].name),
value + reltab[j].addend
);
#endif
*(uint32*)(sectbase + reltab[j].offset) += symtab[sym].value + reltab[j].addend;
}free(reltab);
}
R_SH_DIR32ていうのがリロケーションの種類。
例えばMIPSでは32bit即値load命令がなく、上位16bit loadと下位16bit loadを組み合わせて使うので、それに対して上位16bitリロケーション、下位16bitリロケーションといった種類があるが、ここ(SH)では一種類だけ。
たぶんリンカ/ローダ本に詳しく載ってるんじゃないかな
つづきはソースで
http://dcquake.sourceforge.net/index_j.html
http://sdl-dc.sourceforge.net/
http://www.geocities.co.jp/Playtown/2004/dcdev/index.html
http://dcemulation.org/?title=Games_and_Demos
DreamcastでOS作った#kernelvm 補講 More ログイン