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
123456789
strcpyでもちゃんと動作しているようです。
#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等のいうものがなんとなく解ったということでよしとしました。





