copy_from_user等々とか


copy_from_user等々の関数があります。ユーザプロセスからカーネルプロセスへの引数引渡しに使います。プロセスが違うから。と思っていましたが・・・

その答えはちゃんと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等のいうものがなんとなく解ったということでよしとしました。

最終更新 2010/03/31 15:47:51 - north
(2010/01/13 10:17:21 作成)


検索

アクセス数
3704373
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。