質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
アセンブリ言語

アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

OS

OS(オペレーティングシステム)は、システムソフトウェアの一種であり、一般的に、ハードウェアを直接的に管理・操作する最も中心的な機能を有するソフトウェアがオペレーティングシステムとして呼ばれます。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

アーキテクチャ

アーキテクチャとは、情報システム(ハードウェア、OS、アプリケーション、ネットワーク等)の設計方法、設計思想、設計思想に基づいて構築されたシステム構造をアーキテクチャと呼びます

Q&A

解決済

1回答

1905閲覧

解決しました。:set_gate関数IDT設定 関数が思うように動作しない。

kazuyakazuya

総合スコア193

アセンブリ言語

アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

OS

OS(オペレーティングシステム)は、システムソフトウェアの一種であり、一般的に、ハードウェアを直接的に管理・操作する最も中心的な機能を有するソフトウェアがオペレーティングシステムとして呼ばれます。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

アーキテクチャ

アーキテクチャとは、情報システム(ハードウェア、OS、アプリケーション、ネットワーク等)の設計方法、設計思想、設計思想に基づいて構築されたシステム構造をアーキテクチャと呼びます

0グッド

0クリップ

投稿2020/03/21 05:07

編集2020/03/29 07:38

(内容が乱雑だったので訂正しました。)
IDTのゲートディスクリプタにはaaa関数を登録します。

c

1void protect_puts(char*,int,int,int); 2void set_intr_gate(); 3void aaa(); 4void loop(); 5 6/* メイン関数 */ 7 8void main_kernel(){ 9 10 set_intr_gate(); 11 12 __asm__( 13 "int $0x00\n\t" 14 ); 15 loop(); 16 17} 18 19void loop(){ 20 21 for(; ; ) { 22 23} 24} 25 26void aaa(){ 27 char ptr[] = "abcdefghigk"; 28 char *a = ptr; 29 protect_puts(a,11,5,5); 30} 31 32/* 文字表示関数 */ 33 34void protect_puts(char *char_address,int bytes,int side_size,int height_size){ /* 1=文字アドレス,2=バイト */ 35 36/* message */ 37/* 本来ポインタを使いたいがエラーが起こるのでいまは保留 */ 38 int vram_address = 0xA0000; 39 int font_address = 0x0500; 40 int byte_count = bytes; 41 int yoko = side_size; 42 int tate = height_size; 43 char *ptrr = char_address; 44 45 __asm__ __volatile__( 46 /* 横設定 */ 47 "mov %7,%%eax\n\t" 48 "mov %5,%%ebx\n\t" 49 "add %%eax,%%ebx\n\t" 50 "mov %%ebx,%2\n\t" 51 52 53 "mov %8,%%eax\n\t" 54 "mov $1280,%%ebx\n\t" 55 "mul %%ebx\n\t" 56 "mov %5,%%ebx\n\t" 57 "add %%eax,%%ebx\n\t" 58 "mov %%ebx,%2\n\t" 59 60 61 "mov %6,%%eax\n\t" /* eax = 文字があるアドレス */ 62 "mov %3,%%ebx\n\t" /* %0 ebx = バイト数 */ 63 ".protect_puts_loop2:\n\t" 64 "cmp $0,%%ebx\n\t" 65 "jz .endd\n\t" 66 67 "xor %%esi,%%esi\n\t" 68 "mov (%%eax),%%esi\n\t" /* esiレジスタにASCII1文字 */ 69 /* "mov $97,%%esi\n\t" */ 70 "and $0b00000000000000000000000011111111,%%esi\n\t" 71 "shl $4,%%esi\n\t" 72 73 "xor %%ebx,%%ebx\n\t" 74 "mov %4,%%ebx\n\t" 75 "add %%ebx,%%esi\n\t" 76 77 78 "mov %5,%%edi\n\t" 79 80 "mov $16,%%ecx\n\t" /* 1文字 縦16bitだから */ 81 82 ".protect_puts_loop:\n\t" 83 "movsb\n\t" 84 "add $80 - 1,%%edi\n\t" 85 "loop .protect_puts_loop\n\t" 86 87 "inc %%eax\n\t" 88 89 "xor %%ebx,%%ebx\n\t" 90 91 "mov %5,%%ebx\n\t" 92 "add $1,%%ebx\n\t" 93 "mov %%ebx,%2\n\t" 94 95 "mov %3,%%ebx\n\t" /* バイトカウント 0かどうか確認 */ 96 "sub $1,%%ebx\n\t" 97 "mov %%ebx,%0\n\t" 98 "mov %3,%%ebx\n\t"/* 変更 */ 99 100 "jmp .protect_puts_loop2\n\t" 101 102 103 ".endd:\n\t" 104 105 106 : "=m" (byte_count), /* %0 バイト数 */ 107 "=m" (font_address), 108 "=m" (vram_address) 109 110 : "m" (byte_count), /* %0 バイト数 %4*/ 111 "m" (font_address), 112 "m" (vram_address), 113 "m" (ptrr), 114 "m" (yoko), 115 "m" (tate) 116 ); 117 118 } 119 120 121#define _set_gate(gate_addr,type,dpl,addr,seg) \ 122do { \ 123 int __d0, __d1; \ 124 __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \ 125 "movw %4,%%dx\n\t" \ 126 "movl %%eax,%0\n\t" \ 127 "movl %%edx,%1" \ 128 :"=m" (*((long *) (gate_addr))), \ 129 "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \ 130 :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ 131 "3" ((char *) (addr)),"2" ((seg) << 16)); \ 132} while (0) 133 134void set_intr_gate() 135{ 136 void(* a)(); 137 a = aaa; 138 139 _set_gate(0x8400,14,0,&a,0x08); 140} 141

LinuxカーネルからIDT登録関数を持ってきました。

ブートローダープログラムと上記のカーネル(?)プログラムをrawバイナリ形式に変換し
くっつけてVirtualbox上で動かします。

_set_gate(0x8400,14,0,&a,0x08);

これでIDTにゲートディスクリプタの登録を行っています。
このプログラムを使ってaaa関数(ポインタaにaaa関数のアドレスが入っている。)
をソフトウエア割り込みで呼び出せるようにしたいです。

aaa関数の中で画面に文字を表示させる関数を読んでいるので

ソフトウエア割り込み(INT 0x00)

登録したゲートディスクリプタ(aaa関数への)が参照されaaa関数が呼ばれる。

aaa関数内部で画面に文字が表示される関数が呼ばれ
画面上に文字が表示される。

以上のことを期待しています。

試験的に メモリ0x8600上に
jmp $(無限ループ)
この命令を配置し、
_set_gate(0x8400,14,0,0x8600,0x08);
とやってINT 0x00でソフトウエア割り込みを起こしたところ正常に動作しました。

なので、・・・
void(* a)();
a = aaa;
_set_gate(0x8400,14,0,&a,0x08);

このようにしてaaa関数へのゲートディスクリプタを作成してINT 0x00で呼び出したのですが
エラーこそ起こらないものの画面に何も表示されず
期待通りの動作になりません。

0x8600に配置したループ処理が呼べたことから
割り込み処理ルーチンのアドレス設定は問題なくできているものと思われます。(たぶん)

なので、どちらかと言えば
aaa関数 あるいは aaa関数内で呼ばれているprotect_puts(文字表示関数)のほうに問題があると思うのですが・・・

何がいけないんでしょうか?
アセンブリ言語でいうところのret命令みたいのが必要なのでしょうか?
(いや、それだったらエラーになるはずだが・・・)

c

1/* プロトタイプ宣言 0x1060 */ 2void loop(); 3void protect_puts(char*,int,int,int); 4void set_intr_gate(); 5void aaa(); 6 7/* メイン関数 */ 8 9void main_kernel(){ 10 11 set_intr_gate(); 12 13 14 __asm__( 15 "int $0x32\n\t" 16 ); 17 18 loop(); 19 20} 21 22 23 24 25void aaa(){ 26 char ptr[] = "abcdefghigk"; 27 char *a = ptr; 28 protect_puts(a,11,5,5); 29 30} 31 32/* 文字表示関数 */ 33 34void protect_puts(char *char_address,int bytes,int side_size,int height_size){ /* 1=文字アドレス,2=バイト */ 35 36 37 int vram_address = 0xA0000; 38 int font_address = 0x0500; 39 int byte_count = bytes; 40 int yoko = side_size; 41 int tate = height_size; 42 char *ptrr = char_address; 43 44 __asm__ __volatile__( 45 /* 横設定 */ 46 "mov %7,%%eax\n\t" 47 "mov %5,%%ebx\n\t" 48 "add %%eax,%%ebx\n\t" 49 "mov %%ebx,%2\n\t" 50 51 52 "mov %8,%%eax\n\t" 53 "mov $1280,%%ebx\n\t" 54 "mul %%ebx\n\t" 55 "mov %5,%%ebx\n\t" 56 "add %%eax,%%ebx\n\t" 57 "mov %%ebx,%2\n\t" 58 59 60 "mov %6,%%eax\n\t" /* eax = 文字があるアドレス */ 61 "mov %3,%%ebx\n\t" /* %0 ebx = バイト数 */ 62 ".protect_puts_loop2:\n\t" 63 "cmp $0,%%ebx\n\t" 64 "jz .endd\n\t" 65 66 "xor %%esi,%%esi\n\t" 67 "mov (%%eax),%%esi\n\t" /* esiレジスタにASCII1文字 */ 68 /* "mov $97,%%esi\n\t" */ 69 "and $0b00000000000000000000000011111111,%%esi\n\t" 70 "shl $4,%%esi\n\t" 71 72 "xor %%ebx,%%ebx\n\t" 73 "mov %4,%%ebx\n\t" 74 "add %%ebx,%%esi\n\t" 75 76 77 "mov %5,%%edi\n\t" 78 79 "mov $16,%%ecx\n\t" /* 1文字 縦16bitだから */ 80 81 ".protect_puts_loop:\n\t" 82 "movsb\n\t" 83 "add $80 - 1,%%edi\n\t" 84 "loop .protect_puts_loop\n\t" 85 86 "inc %%eax\n\t" 87 88 "xor %%ebx,%%ebx\n\t" 89 90 "mov %5,%%ebx\n\t" 91 "add $1,%%ebx\n\t" 92 "mov %%ebx,%2\n\t" 93 94 "mov %3,%%ebx\n\t" /* バイトカウント 0かどうか確認 */ 95 "sub $1,%%ebx\n\t" 96 "mov %%ebx,%0\n\t" 97 "mov %3,%%ebx\n\t"/* 変更 */ 98 99 "jmp .protect_puts_loop2\n\t" 100 101 102 ".endd:\n\t" 103 104 105 : "=m" (byte_count), /* %0 バイト数 */ 106 "=m" (font_address), 107 "=m" (vram_address) 108 109 : "m" (byte_count), /* %0 バイト数 %4*/ 110 "m" (font_address), 111 "m" (vram_address), 112 "m" (ptrr), 113 "m" (yoko), 114 "m" (tate) 115 ); 116 117 } 118 119 120 121/* IDT登録関数 */ 122 123#define _set_gate(gate_addr,type,dpl,addr,seg) \ 124do { \ 125 int __d0, __d1; \ 126 __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \ 127 "movw %4,%%dx\n\t" \ 128 "movl %%eax,%0\n\t" \ 129 "movl %%edx,%1" \ 130 :"=m" (*((long *) (gate_addr))), \ 131 "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \ 132 :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ 133 "3" ((char *) (addr)),"2" ((seg) << 16)); \ 134} while (0) 135 136void set_intr_gate() 137{ 138 void(* a)() = aaa; 139 _set_gate(0x8590,14,0,&a,0x08); /* 0x8600 */ 140} 141 142 143 144 145 146/* ループ関数 */ 147 148 void loop(){ 149 150 for(; ; ) { 151 152} 153} 154

文字制限のためPIC初期化関数とかは省略しました。main_kernelから処理がスタートします。
INT 0x32(10進数50)でaaa関数を呼び出そうとしています。
(何やら長いですが原因が全く分からないのでお願いします・・・。)

やってみたこと

IDTアドレスの指定が間違えている可能性がある。
なので・・・
ブートローダーからカーネルへ制御が移行する際に

push IDT

とすることでスタックにIDTアドレスを保管し
カーネルに制御が移ったらスタックに積まれたアドレスを回収する。

c

1/* プロトタイプ宣言 0x1060 */ 2void loop(); 3void protect_puts(char*,int,int,int); 4void set_intr_gate(int); 5void aaa(); 6void test(); 7 8 9/* メイン関数 */ 10 11void main_kernel(){ 12 int idt_address = 0x00000000; 13 __asm__( 14 "mov 4(%%esp),%%eax\n\t" 15 "mov %%eax,%1\n\t" 16 :"=m" (idt_address) 17 :"m" (idt_address) 18 ); 19 20 set_intr_gate(idt_address); 21} 22 23void set_intr_gate(int idt_address) 24{ 25 void(* a)() = aaa; 26 _set_gate(idt_address,14,0,&a,0x08); /* 0x8600 */ 27} 28 29

こちらも結果は同様に変わりませんでした。(1万文字をこえるので変更点以外は省略しました。)
そもそもIDTにトラップゲートを設置する以前に何か設定するものでもあるのでしょうか?
何か知っておられましたら教えていただけると助かります。

lidt [IDTR] mov eax,int_handler mov [IDT+49*8],ax mov word [IDT+49*8+2],0x08 mov word [IDT+49*8+4],0x8E00 shr eax,16 mov [IDT+49*8+6],ax int 49

ブートローダーで上記のプログラムでIDTに登録したところうまくできましたが
カーネル以降での登録はまだうまくいっていません。

ブートローダーはアセンブリ言語で書き
カーネル以降はC言語で記述しており

アセンブリ言語で定義した関数を登録しソフトウエア割り込みで呼び出す分には何も問題なく作動するのですが
カーネルつまりC言語で定義された関数を登録すると呼び出した時点でエラー・暴走が起こります。
呼び出した時点で暴走するので関数から復帰する際に~ってわけではなさそうです。

void function(){}

なにもしなってのはこのようなやつです。

_set_gate(idt_address,14,0,ブートローダーアセンブリ言語レベルで定義した関数アドレス,0x08);

この場合はうまくいき

_set_gate(idt_address,14,0,カーネルCC言語レベルで定義すた関数アドレス,0x08);

この場合エラーになります。(内容に関係なしに)

アセンブリ言語内で定義した関数は割り込みハンドラとして登録し実行することができる。
これを利用して解決案として
アセンブリ言語内で割り込みハンドラを登録。
そのハンドラ内でC言語関数アドレスに向けてJMPする。

・・・この方法を試そうと思いましたが
アセンブリ言語で書かれたブートローダーと
C言語で書かれたカーネルをそれぞれ
別々にリンクしrawバイナリ形式にしたところで繋げているのでこの方法は使えません・・・。

なんか解決策はないでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

asm

2020/03/28 01:00

そもそも void(* a)() = aaa; a(); の動作は確認しましたか?
kazuyakazuya

2020/03/28 02:02 編集

それはやっていませんでした。 (その使い方は知らなかった。) C関数の中身がどんな内容であろうと ソフトウェア割り込みを起こした時点で暴走します。 復帰する前に暴走するのです。 ただ、なんだかんだかなりめんどくさい方法で登録し呼び出すことに成功しました。 (結局なぜ直接C関数を割り込みハンドラとして登録できないのはわからなかった。) ただ、iretなどで復帰がてきないので 割り込み時に積まれる復帰アドレスを 回収し そこへジャンプできないか 検証中です。 (解決できたら方法を追記します。)
kazuyakazuya

2020/03/29 07:38

復帰できなかったのではなく暴走を起こしていました。 割り込みハンドラが呼び出された時点でレジスタを保存せずに そのまま復帰しようとしたので暴走を起こしていました。
guest

回答1

0

自己解決

自己解決しました。(直接的な解決方法ではない・かなりめんどくさい)
(一応、メモ的に保存したいので詳しく書きます。)

まず前提として
ブートローダープログラムはnasm(アセンブリ言語)
それ以降に(直後)設置するカーネルはgcc(C言語)

それぞれをrawバイナリ形式に変換し、バイナリエディタでそのままくっつけています。
この方法の弱点として両者をリンクを行わないため
アセンブリ言語側で定義したラベルを参照できないなどのかなり致命的な(?)問題がありますが
事前に設置するアドレスを確認しておくことでなんとかします。

(注意点としてディスクイメージ「rawバイナリ形式」のファイルのサイズは512の倍数でないといけません。でないとエラーになります。)

割り込みハンドラをソフトウエア割り込みで呼び出せるようにするには
IDTを初期化し、(IDTを定義したうえでLIDT)
そのIDTの中にトラップゲートを設置します。

で、今回私が遭遇した問題というのは
ブートローダー側(純アセンブリ言語で作った)で定義した割り込みルーチンは登録できるのに
カーネル(C言語)側で定義したルーチンは登録できない。

逆に言えばアセンブリ言語側に割り込みハンドラを設置しそこから
カーネル(C言語で書かれた)で書かれた処理に飛ばす仕組みを作れば
カーネルで割り込み処理を行える!

こんな感じで・・・(概念図)

image

1---ブートローダー(アセンブリ言語)--- 2 3 4BOOT_LOAD equ 0x7C00 5ORG BOOT_LOAD 6 7;/_/_/_/_/_/_/_/ 8;マクロ 9;/_/_/_/_/_/_/_/ 10%include "../include/marco.s" 11%include "../include/define.s" 12 13;/_/_/_/_/_/_/_/ 14;BPB 15;/_/_/_/_/_/_/_/ 16 17section .text 18 19entry: 20jmp ipl 21times 90 - ($ - $$) db 0x90 ;先頭から90バイトまでをnop命令で埋め尽くす。 22 23 24;・ 25;・ 26;・ずらずら書いていますがつまりはブート処理ほぼ省略 27;・ 28;・ 29 30 31;C言語側で定義した関数は割り込み処理として登録できないが 32;登録自体はC言語側できる。(登録はもちろんアセンブリ言語側でもできる) 33 34jmp next 35 36sample: 37 jmp 0xB802;0xB800 + 0x02 38 39 40next: 41 42set_trap_gate1: ;IDTアドレスをeaxレジスタにセットする割り込みルーチン(関数名:get_idt_address) 43 mov eax,get_idt_address 44 mov [IDT],ax 45 mov word [IDT+2],0x08 46 mov word [IDT+4],0x8E00 47 shr eax,16 48 mov [IDT+6],ax 49 50 51;↓ 0x01でソフトウエア割り込みを起こすとsample:ラベルの処理に移る 52 53set_trap_gate2: ;C言語側で定義したい割り込みハンドラ 54 mov eax,sample ;sampleはラベル(名前は適当) 55 mov [IDT + 0x08],ax 56 mov word [IDT+2 + 0x08],0x08 57 mov word [IDT+4+0x08],0x8E00 58 shr eax,16 59 mov [IDT+6+0x08],ax 60 61 62jmp end_boot 63 64 65times (1024 * 15) - ($ - $$) db 0 ;1024*15まで0x00で埋め尽くす 66 end_boot:;0xB800 67jmp $ ;ここはバイナリディタで取り除く ここ(無限ループ処理命令)が消えるとすぐしたのカーネルに制御が移ります! 68 69---ブートローダー(アセンブリ言語)--- 70 71 72 73---カーネル(C言語)--- 74 75void main_kernel(){ 76 __asm__("jmp start_kernel_c\n\t"); /*EB06*/ 77 78 __asm__("jmp aaaa\n\t"); /*EBE4*/ 79 80 __asm__("start_kernel_c:\n\t"); 81 82 83__asm__( 84 "int $0x01\n\t" 85 ); 86 87 loop(); 88} 89 90 91 92 93void aaa(){ 94 95 96 97 __asm__( 98 "aaaa:\n\t" 99 "push %eax\n\t" 100 "push %ebx\n\t" 101 "push %ecx\n\t" 102 "push %esi\n\t" 103 "push %edi\n\t" 104 ); 105 char ptr[] = "abcdk"; 106 char *a = ptr; 107 protect_puts(a,5,5,5); 108 109 __asm__( 110 "pop %edi\n\t" 111 "pop %esi\n\t" 112 "pop %ecx\n\t" 113 "pop %ebx\n\t" 114 "pop %eax\n\t" 115 "iret\n\t" 116 ); 117} 118---カーネル(C言語)--- 119 120

sample:
jmp 0xB802;0xB800 + 0x02
とありますが
カーネルはアドレス0xB802から始まる。(0x7C00 + 1024 * 15)

+0x02の場所に__asm__("jmp aaaa\n\t");があります。
(jmp命令は基本的に2バイト ジャンプ距離が遠いと2バイトより大きくなるそうです。)

さっそくrawバイナリ形式に変換しそのままくっつけたいところですが・・・
ちょっと作業が入ります。

イメージ説明
ここがブートローダー(アセンブリ言語)のjmp $です。
邪魔なので消します。(消さないと永遠にカーネルに制御が移りません。)

イメージ説明
青い場所が関数が始まると必ず最初につく何かです。(これがなんなのかは私は知らない。)
ピンクの場所がjmp命令(カーネルの最初に置いたやつ。)です。
jmp命令がカーネルの一番最初に来てほしいので青の場所とピンクの場所を交代させます。

jmp命令は EB オフセット となるようです。
今、6バイト分ずらしましたので・・・

EB 02を +6をやって EB 08
EB 62を +6をやって EB 68

と書き直します。
これでできました!。

ただ、いちいち書くのがめんどくさいしC言語側からIDTを登録することができないのは面白くないので
もし直接的解決に繋がるやりかた、私のやり方で間違えているところがある場合教えてください。

投稿2020/03/29 07:36

編集2020/03/29 07:43
kazuyakazuya

総合スコア193

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuyakazuya

2020/03/29 07:52

>C言語側からIDTを登録することができないのは面白くないので アドレスを調べれば アセンブリ言語側へ処理を移しそこからC言語で定義されたルーチンへ処理を飛ばせば できそうですが めんどくさいのでたぶんやりません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問