copy_from_user等々とか
copy_from_user等々の関数があります。ユーザプロセスからカーネルプロセスへの引数引渡しに使います。プロセスが違うから。と思っていましたが・・・
その答えはちゃんとLinuxカーネル解読室にかいてありました。解読室ではput_user_u64で説明されています。要はマシン語レベルでエラー処理を関数自体に持たせるため。ということです。
記述は、本文処理部+エラー処理部+エラーテーブルと言う構成で、エラー処理部は.fixup、エラーテーブルは__ex_tableと宣言しておくようです。
copy_form_userから呼び出される(copy_to_userからも呼び出されている。)__copy_userを見てみようと思います。アセンブラですからアーキテクチャに依存していろんなファイルがありました。
で、転送コードのそれぞれのラベルとジャンプ先をsection __ex_tableに登録しています。もし、ここでエラーが発生すると、例外が発生しdo_page_faultが呼ばれます。CoWのような正規な例外もあるわけで、ここではその例外の要因をチェックして所定の処理へと進みます。本ケースでは、fixup_exception関数がコールされ、そこでは例外をおこしたIPを引数にsearch_exception_tables関数で、exception_table_entryを取得し、そこで設定されているアドレスにジャンプするようです。
copy_from_useがエラーハンドルの組み込みに起因するということで、プロセス空間の違いからというわけでなさそうです。実際カーネルはユーザプロセスのmm_structを使います。ということは同じGDTで呼び出しユーザプロセスのメモリー空間・・・。それならstrcpyでも動作するのでは?
ユーザテストプログラム
で、strcpyを見てみました。これもいろいろあってその中の2つ程みてみました。ただ単にdesへsrcをコピーしているだけで、エラーハンドルに掛かる処理はありません。
その答えはちゃんとLinuxカーネル解読室にかいてありました。解読室ではput_user_u64で説明されています。要はマシン語レベルでエラー処理を関数自体に持たせるため。ということです。
記述は、本文処理部+エラー処理部+エラーテーブルと言う構成で、エラー処理部は.fixup、エラーテーブルは__ex_tableと宣言しておくようです。
copy_form_userから呼び出される(copy_to_userからも呼び出されている。)__copy_userを見てみようと思います。アセンブラですからアーキテクチャに依存していろんなファイルがありました。
#define __copy_user(to, from, size) \ do { \ int __d0, __d1, __d2; \ __asm__ __volatile__( \ " cmp $7,%0\n" \ " jbe 1f\n" \ " movl %1,%0\n" \ " negl %0\n" \ " andl $7,%0\n" \ " subl %0,%3\n" \ "4: rep; movsb\n" \ " movl %3,%0\n" \ " shrl $2,%0\n" \ " andl $3,%3\n" \ " .align 2,0x90\n" \ "0: rep; movsl\n" \ " movl %3,%0\n" \ "1: rep; movsb\n" \ "2:\n" \ ".section .fixup,\"ax\"\n" \ "5: addl %3,%0\n" \ " jmp 2b\n" \ "3: lea 0(%3,%0,4),%0\n" \ " jmp 2b\n" \ ".previous\n" \ ".section __ex_table,\"a\"\n" \ " .align 4\n" \ " .long 4b,5b\n" \ " .long 0b,3b\n" \ " .long 1b,2b\n" \ ".previous" \ : "=&c"(size), "=&D" (__d0), "=&S",(__d1), "=r"(__d2) \ : "3"(size), "0"(size), "1"(to), "2"(from) \ : "memory"); \ } while (0)x86拡張インラインアセンブリっていうやつで「うん・・・」って感じです。転送サイズが7文字以下の時は、そのままラベル1でバイト転送rep; movsb、もしそうでないなら、転送先アドレスの8バイトアライメント分、ラベル4でバイト転送したのち、ラベル0でダブルワード転送して、ダブルワード転送しきれなかった分をラベル1でバイト転送してるっぽいです。ラベル4でエラーが発生したらレベル5へ、レベル0で発生したらラベル3へ、ラベル1でエラーが発生したらラベル2へって具合です。.fixup、__ex_tableはセクションが異なるため本体処理はラベル2で終了です。エラー処理はその時の転送したサイズをCXにでもセットしているのかな?
で、転送コードのそれぞれのラベルとジャンプ先をsection __ex_tableに登録しています。もし、ここでエラーが発生すると、例外が発生しdo_page_faultが呼ばれます。CoWのような正規な例外もあるわけで、ここではその例外の要因をチェックして所定の処理へと進みます。本ケースでは、fixup_exception関数がコールされ、そこでは例外をおこしたIPを引数にsearch_exception_tables関数で、exception_table_entryを取得し、そこで設定されているアドレスにジャンプするようです。
copy_from_useがエラーハンドルの組み込みに起因するということで、プロセス空間の違いからというわけでなさそうです。実際カーネルはユーザプロセスのmm_structを使います。ということは同じGDTで呼び出しユーザプロセスのメモリー空間・・・。それならstrcpyでも動作するのでは?
#include <linux/kernel.h> #include <linux/sched.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #define PROC_NAME "hogehoge" static size_t proc_read( char* page, char** start, off_t offset, int count,int* eof, void* data ); static struct proc_dir_entry *dirp; static int proc_init_module(void) { dirp = (struct proc_dir_entry *) create_proc_entry(PROC_NAME, 0666, (struct proc_dir_entry *) 0); if (dirp == 0) return(-EINVAL); dirp->read_proc = (write_proc_t *) proc_read; return 0; } static void proc_cleanup_module(void) { remove_proc_entry(PROC_NAME, (struct proc_dir_entry *) 0); } static size_t proc_write( struct file *filp, const char __user *buff, unsigned long len, void *data ) { char _buff[64]; strncpy(_buff, buff, len); _buff[len] = 0; printk("from user :%s\n", _buff); return len;: } module_init(proc_init_module); module_exit(proc_cleanup_module);
ユーザテストプログラム
#include "stdio.h" main() { FILE* fp; char* buff="123456789"; fp = fopen("/proc/hogehoge", "w"); if (fp) { fwrite(buff, sizeof(char), strlen(buff), fp); fclose(fp); } } [root@localhost test]# ./a.out [root@localhost test]# dmesg 123456789strcpyでもちゃんと動作しているようです。
#include "stdio.h" main() { FILE* fp; char* buff[100]; fp = fopen("/proc/hogehoge", "r"); if (fp) { fgets(0xd0000000, sizeof(buff), fp); printf("%s\n", buff); fclose(fp); } } [root@localhost test]# ./a.out セグメンテーション違反ですが、モジュールは生きていました。カーネルがダウンするのを期待したのですが・・・
で、strcpyを見てみました。これもいろいろあってその中の2つ程みてみました。ただ単にdesへsrcをコピーしているだけで、エラーハンドルに掛かる処理はありません。
char *strcpy(char *dest, const char *src) { char *tmp = dest; while ((*dest++ = *src++) != '\0') /* nothing */; return tmp; } EXPORT_SYMBOL(strcpy); char *strcpy(char *dest, const char *src) { int d0, d1, d2; asm volatile("1:\tlodsb\n\t" "stosb\n\t" "testb %%al,%%al\n\t" "jne 1b" : "=&S" (d0), "=&D" (d1), "=&a" (d2) :"0" (src), "1" (dest) : "memory"); return dest; } EXPORT_SYMBOL(strcpy);strcpyはdes,srcのアドレスをそのまま参照して複写しています。同じメモリー空間ということで可能なのだと思います。チェックの仕方がまずいのかもしれませんが、まあ、copy_from_usr等のいうものがなんとなく解ったということでよしとしました。