2010年12月18日 星期六

Inline Assembly


最近在看kernel的code,裡面有些組語的語法,所以就花點時間把它看了一下,基本上,這邊幾乎都是參考Brennan's Guide to Inline Assembly[1]。

GCC使用的asm是AT&T/UNIX的語法而不是Intel的語法,所以有些不同必須先弄清楚。

GCC 的 inline assembly 基本格式是:
 asm ( assembler template
     : output operands     (optional)
     : input operands     (optional)
     : list of clobbered registers     (optional)
     );
沒用的欄位可以空下來。最後一個欄位是用來告訴GCC在asm code中,我們已經用了哪些register。

Register name:
Register的名稱前面必須加上”%”
  • AT&T:%eax
  • Intel:eax
為了讓GCC的asm能跨平台,所以可以用%0、%1...%n代表後面依序出現的register。


Source/Destination ordering:
AT&T的source永遠在左邊而destination永遠在右邊
  • AT&T:movl %eax, %ebx
  • Intel:mov ebx, eax
  • 您可以在Instruction後面會被加上b、w和l,用以區分operand的size,分別代表byte、word和long,在不加的情況下,gcc會自動判斷,但有可能誤判。



Constant value/immediate value format:
Constant value/immediate value前面必須加上”$”
  • AT&T:movl $boo, %eax
  • Intel:mov eax, boo
  • 將boo的address載到eax中,boo必須是static變數。
#include <stdio.h>

int main(int argc, char *argv[])
{
    static int x __asm__ ("x") = 10;
    int y;

    x = atoi(argv[1]);
    __asm__("movl $x, %0"  // 這邊的%0代表後面出現的第一個register,即y。
            : "=r" (y));
    // output operand前一定要有個"="表示這個constraint是write-only,
    //  "="叫constraint modifier。
    // input output operands後一定要跟著相對應的C 變數的參數,
    // 這是給asm的參數。
    printf("x=%p, y=0x%x\n", &x, y);
    return 0;
}

  • AT&T:movl $0xabcd, %eax
  • Intel:mov eax, abcd
  • 將eax設為0xabcd。
#include <stdio.h>

int main(int argc, char *argv[])
{
    int y;

    __asm__("movl $0xabcd, %0"
            : "=r" (y));

    printf("y=0x%x\n", y);
    return 0;
}


Referencing memory:
  • AT&T:immed32(basepointer,indexpointer,indexscale)
  • Intel:[basepointer + indexpointer*indexscale + immed32]
  • 沒用的欄位可以空下來。
Addressing a particular C variable:
  • AT&T:_booga
  • Intel:[_booga]
#include <stdio.h>

int main(int argc, char *argv[])
{
    static int i __asm__ ("i");
    int io;

    i = atoi(argv[1]);

    __asm__("movl i, %0;\n"
            : "=r"(io));
    printf("i=%d, io=%d\n", i, io);

    return 0;
}

Addressing a variable offset by a value in a register:
  • AT&T:_variable(%eax)
  • Intel:[eax + _variable]
#include <stdio.h>

int main(int argc, char *argv[])
{
    struct ic {
        int i;
        char c;
    };

    int i = 0;
    char c = 0;
    static struct ic ic __asm__ ("ic");

    ic.i = 11;
    ic.c = 'b';

    __asm__("movl $ic, %%eax;\n"
            "mov (%%eax), %0;\n"
            "mov 4(%%eax), %1;\n"
            : "=r"(i), "=r"(c)
            : "m"(ic)
            : "%eax");
    printf("i=%d, c=%c\n", i, c);

    return 0;
}

Addressing a value in an array of integers (scaling up by 4):
  • AT&T:_array(,%eax,4)
  • Intel:[eax*4 + array]
#include <stdio.h>

int main(int argc, char *argv[])
{   
    int i; 
    static int ary[2][3] __asm__("ary") = {
        {0, 1, 2},
        {10, 11, 12}
    };

    // i = ary[1][1]
    __asm__("movl $12, %%eax;\n" // cal how many size of one raw
            "movl $4, %%ebx;\n"  // which column
            "movl ary(%%ebx, %%eax, 1), %0;\n" 
            : "=r" (i)
            :
            : "%eax", "%ebx");
    printf("i=%d\n", i);

    return 0;
}


參考資料
  1. http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html
  2. http://www.study-area.org/cyril/opentools/opentools/x969.html



2010年12月12日 星期日

用一張圖來為 Git 快速入門(Git Cheat Sheet)


用一張圖來為 Git 快速入門(Git Cheat Sheet)












參考資料:
網路



2010年12月11日 星期六

Linux Kernel(13)- syscall


System Call在HW和user space提供一層抽象層,主要目的有:
  • 為user space提供硬體抽象層。比如,讀取檔案時,不用管檔案所在的媒體類型與檔案儲存類型。
  • System call能確保系統的安全與穩定。避免user space的無意或惡意的破壞。

除了exception和trap以外,System call是user space進入kernel space的唯一管道。
User space的programming主要是base on API(Application Programming Interface)並非system call,從programmer的觀點來看,關注的是API(如C library)而非system call。


System call的return type為long,主要是要相容64bit,return value通常代表失敗或成功,失敗時,error code當常寫入global variable “errno”。

典型的system call都以sys_開頭,如getpid()的system call為:
asmlinkage long sys_getpid(void)
{
    return current->tgid;
}

在Linux中(x86),將所有的system call存放在一個system call table中,透過system call number來所引(index)要執行的system call,儘管每個platform所implement的system call table和system call number都不同,但是原理都是相同的,首先會將system call number存放在某個特定的CPU register(X86放在eax),並將system call的參數也存放置其他的register(最多5個參數,x86依序為ebx、ecx、edx、esi和edi),接著透過int 0x80進入system call處理程序,透過system call number(eax)在system call table中找到相對應的system call,並且執行該system call,因為參數存放是先就定義好了,所以就可以在registers(x86依序為ebx、ecx、edx、esi和edi)中依序讀出要處理的參數,超過五個參數就會用structure來傳遞,而ioctl這個不定參數的system call是傳遞pointer的方式來存取,ioctl是一個不好的例子,因為定義不是很明確,system call最好是能定義明確。


新增system call “brook()”到kernel 2.6.32的步驟(x86)

  1. 新增一筆system call entry到sys_call_table中arch/x86/kernel/syscall_table_32.s。
  2. 直接在最後面加入”.long sys_brook”

  3. 定義brook的system call number,arch/x86/include/asm/unistd_32.h,並將NR_syscalls做遞增。
  4. #define __NR_brook              337
    #define __NR_syscalls           338
    

  5. 定義system call的原型,include/linux/syscalls.h。
  6. asmlinkage long sys_brook(int n, unsigned long arg);
    

  7. 加入至system call table中,arch/x86/kernel/syscall_table_32.S。
  8. .long sys_brook;
    

  9. 撰寫system call的內容。
  10. 建立一個新的資料夾名為”brook_syscall”,並將這個資料夾加入Makefile的core-y+=中。 brook_syscall/Makefile
    obj-y := brook.o
    

    brook_syscall/brook.c
    #include <linux/kernel.h>
    #include <linux/syscalls.h>
    #include <linux/uaccess.h>
    
    SYSCALL_DEFINE2(brook, int, n, unsigned long, arg)
    {
        int __user *p = (int __user *) arg;
        int i, x, sum = 0, err = 0;
        printk("n=%d, ", n);
        for (i = 0; i < n; i++) {
            err = get_user(x, p + i);
            sum += x;
            if (err) {
                return err;
            }
            printk("[%d]=%d, ", i, x);
        }
    
        return sum;
    }
    

    頂層的Makefile要將brook_syscall加入core-y中
    ifeq ($(KBUILD_EXTMOD),)
    core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ brook_syscall/
    

  11. 撰寫Application測試system call
  12. 這邊我們撰寫一個類似C library這層的功能"brook_app/brook.h"
    #include <linux/unistd.h>
    #include 
    #define __NR_brook 337
    int brook(int n, ...)
    {
        int ret;
        va_list ap;
    
        va_start(ap, n);
        ret = syscall(__NR_brook, n, ap);
        va_end(ap);
        return ret;
    }
    

    application呼叫brook(),再由brook()轉call我們的system call "sys_brook()"
    #include <stdio.h>
    #include "brook.h"
    int main(int argc, char *argv[])
    {
        return printf("%d\n", brook(3, 3, 2, 1));
    }
    


Kernel Version:2.6.32
參考資料:
  • Linux Kernel Development 2nd, Novell Press
  • http://pradeepkumar.org/2010/01/implementing-a-new-system-call-in-kernel-version-2-6-32.html
  • Professional Linux Kernel Architecture, Wiley Publishing


熱門文章