@earthen94

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

OS自作 カーネル→ユーザスタック切り替え後に期待したスタックにならない

背景

自作OSを作成しています。

投稿時点 : https://github.com/ooe1220/KansoOS/tree/0693566b02a75e84ce68ac64759b0ed4208471d8
最新 : https://github.com/ooe1220/KansoOS
※編集中のソースはまだあげていません。

問題

従来は以下の様になっていました。(ソースはGIT内)

user_exec.c(カーネル) : call start
start.S(ユーザプログラム入口) : jmp main
test3.c : ret(return 0)でuser_exec.cへ返る

そこで以下のように書き換えました。

user_exec.S(カーネル) : espをユーザ用へ書き換え。jmp start ※EXITシステムコールからラベル指定でuser_execに戻る為、CからSへ書き換えました
start.S(ユーザプログラム入口) : jmp main
test3.c : ret(return 0)でstart.Sへ返る
start.S : int0x80でexitシステムコールを呼び出す(今回はここまで到達していないため割愛)

従来は正常に動いていたのですが、今回ユーザスタック切り替え、カーネル(user_exec.S)→ユーザ(test3.c)をjmpに変更したところ、
プログラムがtest3.c内で止まってしまうようになりました。
test3.cスタックを可視化したところ、全然期待スタックになっていませんでした。
もう何日もはまっており、どこでスタックが壊れたのか分かりません。
詳しい方がおられたら教えていただけませんでしょうか。

ソース

user_exec.S
.intel_syntax noprefix
.global user_exec
.global user_exec_ret

/* kernelスタック退避用 */
.section .bss
    .global kernel_stack_saved
kernel_stack_saved:
    .space 4          /* resd 1 相当 */

.section .text

/* void* entry, int argc, char** argv */
user_exec:

    /* ESPに渡された引数を取得 (cdecl, 32bit) */
    /* [esp]   = return address */
    /* [esp+4] = entry */
    /* [esp+8] = argc */
    /* [esp+12]= argv */
    mov eax, DWORD PTR [esp+4]   /* entry */
    mov esi, DWORD PTR [esp+8]   /* argc */
    mov edi, DWORD PTR [esp+12]  /* argv */
    
    /* カーネルスタック退避 */
    mov ebx, esp
    mov DWORD PTR [kernel_stack_saved], ebx

    /* ユーザースタックに切替 */
    #mov esp, 0x10000
    mov esp, 0x30000
    
    /* ユーザスタックに argc/argv を push */
    push edi   /* argv */
    push esi   /* argc */

    /* ユーザプログラムへ飛ぶ */
    jmp eax

/* exit から戻るラベル */
user_exec_ret:
    mov esp, DWORD PTR [kernel_stack_saved]  /* カーネルスタック復元 */
    ret
start.S
.intel_syntax noprefix
.global _start
.extern main

_start:
    call main           # Cのmainを呼ぶ

    # exit syscall
    mov eax, 0          # syscall番号 0 = exit
    int 0x80
test3.c
#include "lib/mystdio.h"

int main(int argc, char **argv){

    //asm volatile("hlt");

    volatile unsigned short *vram = (unsigned short*)0xB8000;
    unsigned char hex[] = "0123456789ABCDEF";

    uint32_t *bp;
    asm volatile("mov %%ebp, %0" : "=r"(bp)); // EBPを取得

    int x = 0, y = 0;

    // --- EBPの値を1行目に表示 ---
    // --- EBPの値を1行目に表示 ---
    char *label = "EBP=";
    for(int i = 0; label[i]; i++)
        vram[y*80 + x + i] = (0x0F << 8) | label[i];

    x += 4; // ラベル分だけ右にずらす
    uint32_t ebp_val = (uint32_t)bp;
    for(int j = 0; j < 8; j++){
        int d = (ebp_val >> (28 - j*4)) & 0xF;
        vram[y*80 + x + j] = (0x0F << 8) | hex[d];
    }

    y++;    // 次の行
    x = 0;  // 行先頭に戻す

    // --- スタックダンプ(高アドレス順) ---
    // 引数(3)からローカル変数(-2)まで逆順に表示
    for(int i = 3; i >= -2; i--){
        uint32_t addr = (uint32_t)&bp[i]; // アドレス
        uint32_t val  = bp[i];            // 値

        // アドレスを8桁16進で表示
        for(int j = 0; j < 8; j++){
            int d = (addr >> (28 - j*4)) & 0xF;
            vram[y*80 + x + j] = (0x0F << 8) | hex[d];
        }

        vram[y*80 + x + 8] = (0x0F << 8) | ':'; // 区切り

        // 値を8桁16進で表示
        for(int j = 0; j < 8; j++){
            int d = (val >> (28 - j*4)) & 0xF;
            vram[y*80 + x + 9 + j] = (0x0F << 8) | hex[d];
        }

        y++; // 次の行へ
    }

    printf_d("test3 argc = %d\n", argc);
    
    int i=0;
    for(;i<argc;i++){
        write(argv[i]);
    }
    
    return 0;
}

検証

test3.cの中でhlt実行し、qemuでスタックの中身を表示。
何も入っておらず、EBP及びESPがかけ離れているのも気になります。

(qemu) x/4xw $esp 
0002ff2c: 0x00000000 0x00000000 0x00000000 0x00000000
(qemu) info registers 
EAX=00010000 EBX=0009fa8c ECX=00000001 EDX=00000021
ESI=00000001 EDI=0009facc EBP=0009fb28 ESP=0002ff2c

Cでスタックを表示してみる

图片.png

EBP=9fb28
ここで
返り値はEBP+4を見てみると0x8369
その付近を見てみる。

(qemu) x/10i 0x8364
0x00008364:  call   0x8550
0x00008369:  add    $0x10,%esp <<<返りアドレス
0x0000836c:  jmp    0x82ec
0x00008371:  lea    0x0(%esi),%esi
0x00008378:  call   0x9450
0x0000837d:  cmp    $0xa,%al
0x0000837f:  je     0x82d7
0x00008385:  cmp    $0x8,%al
0x00008387:  jne    0x8307
0x0000838d:  call   0x9450

でも実際はstartからtest3.cへ跳んでいるので返りアドレス付近は以下の様になっているはずです。。。

call main
mov eax, 0
int 0x80

期待したスタック

EBP+12 **argv
EBP+8 argc
EBP+4 返りアドレス(start.S)

test3.cの機械語

test@test-fujitsu:~/kaihatsu/KansoOS/build$ objdump -d test3.elf

00010000 <_start>:
   10000:	e8 0b 00 00 00       	call   10010 <main>
   10005:	b8 00 00 00 00       	mov    $0x0,%eax
   1000a:	cd 80                	int    $0x80
   1000c:	66 90                	xchg   %ax,%ax
   1000e:	66 90                	xchg   %ax,%ax

00010010 <main>:
   10010:	55                   	push   %ebp
   10011:	57                   	push   %edi
   10012:	56                   	push   %esi
   10013:	53                   	push   %ebx
   10014:	81 ec b8 00 00 00    	sub    $0xb8,%esp

...(省略)...

   1026b:	5f                   	pop    %edi
   1026c:	5d                   	pop    %ebp
   1026d:	c3                   	ret   

mainの中で以下が生成されていないのが気になります

push ebp
mov ebp.esp
0 likes

3Answer

ICEとかデバッガーでステップ実行、トレース実行はできないのですか?
そういう環境があればすぐにわかることだと思うし、そういうのを準備して開発を進めたほうがいいと思いますよ。
回答じゃなくて申し訳ないですが。

0Like

Comments

  1. @earthen94

    Questioner

    @itagakiさん
    御助言ありがとうございます。
    一応qemuのデバッグがice相当ではあると思います。コードのアドレスが分からなくてブレイクポイントを貼りづらいので、hlt命令でcpuを止めてレジスタを見たり、値を16進数に変換してvramに直書きする以外に思いつきませんね。。かなり原始的ですが。。

    追記
    次の行へcallした直後にpopしたら実行中のアドレスが取れるのでそうしたらステップ実行出来そうです。お陰様で思いつきました。

関数コールにおいて、スタックがどのように使われるのか整理したほうが良いかと思います。
正しくスタックに積めていない状況で、ユーザースタックへの切り替えもやろうとしているので問題が複雑化して見えているようです。

まずは、
 call start を jumpを使うコードに書き換える
というところから挑戦してみるべきかと。(自分でスタックに積んで呼ぶ)

なお、最後の疑問ですが、
push ebp   ← これはありますね
mov ebp.esp ← これは必須ではありません。
コンパイラの判断でebpを使わないことはよくあります。

0Like

Comments

-fno-omit-frame-pointerをつけてコンパイルする必要があるんだと思うな。

0Like

Your answer might help someone💌