顯示具有 Linux 標籤的文章。 顯示所有文章
顯示具有 Linux 標籤的文章。 顯示所有文章

2012年12月19日 星期三

[C]使用getopt()來parsing command line的參數

在Linux底下我們經常會使用'-'來設定一些參數,比如說grep使用-n來設定顯示搜尋到的行數。
grep -n "test" ./*
當我們自己寫Linux程式時有沒有比較快的方法能parsing這些參數呢?Linux底下有個好用的function叫getopt(),他可以幫住我們來parsing這些參數。要使用getopt()必須要include unistd.h,以下是一個範例:
#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    char ch;
    int count;
    while ((ch = getopt(argc, argv, "d:t")) != EOF) {
        switch (ch) {
        case 'd':
            printf("-d %s\r\n", optarg);
            break;
        case 't':
            printf("-t\r\n");
            break;
        default:
            fprintf(stderr, "Unknown option: '%s'\n", optarg);
            return 1;
        }

    }
    //skip the option we read
    argc -= optind;
    argv += optind;

    for (count = 0; count < argc; count++) {
        puts(argv[count]);
    }
    return 0;
}
sway@mac$ gcc getopt.c -o getopt
sway@mac$ ./getopt -d test -t apple banana
-d test
-t
apple
banana
這邊要注意的是第8行getopt()的第三個參數"d:t"。d後面的:表示-d之後需要再指定一個參數,而此參數可在optarg中取得。t後面沒有:號表示之後不需要再指定參數。

第27行的puts會把不屬於-d -t的剩下所有參數都印出來,optind其實就是前面while迴圈處理到第幾個參數的index。

2012年6月8日 星期五

ELF中的.bss section和COMMON section

大家都知道未初始化的global變數會被配置在.bss section中。不過在ELF中沒有這麼簡單,ELF多了一個COMMON section專門用來存放未初始化的global變數。怎麼會有兩個地方都用來存放未初始化的global變數呢?其實是有差別的,以下我們舉個例子來說明:
int a = 1;            //.data section
int b = 0;            //.bss section
int c;                //COMMON section
由上面例子可以知道,如果我們把global變數初始化為非0的值,會被放在.data section。如果把global變數初始化為0,會被直接放到.bss section。如果完全沒初始化的話就會被放到COMMON section。為什麽要多一個COMMON section呢?其實跟gcc linker的運作有關係。

以上面例子為例,global變數c其實是一個弱型別。也就是說,如果我們在不同的檔案中都宣告global變數c而沒有初始化的話,gcc linker在做linking時後並不會產生error。
//test1.c
int c;

//test2.c
int c;
上面例子是完全合法的,我們也可以定義一個強型別來把弱型別蓋掉。
//test1.c
int c;

//test2.c
int c;

//test3.c
int c = 1;    //strong type
上面例子gcc linker在做linking時候如果讀到test1.c中的int c,linker會先把c放到COMMON section中。讀到test2.c的c時,因為發現重複定義,所以不會做處理。讀到test3.c中的c由於是強型別,因此會把之前在COMMON的c蓋掉,在.data section建立一個初始值為1的c。因次最後的結果我們只會在.data section中找到c這個變數,不會在COMMON中看到c這個變數。

當然如果同時定義兩個強型別變數的話,Linker就會跟你抗議了。
//test1.c
int c = 1;    //strong type

//test2.c
int c = 2;    //strong type
test2.o:(.data+0x0): multiple definition of `c'
test1.o:~/sway/test1.c:4: first defined here
collect2: ld returned 1 exit status
由上面這些例子可以知道,COMMON其實主要的用途是用來讓linker做merge用的。因此uninitialized的global變數會被暫時放在COMMON section,等Linker做完merge之後再看情況搬到正確的section中,也可能繼續留在COMMON section。因為這種特性,大多數embedded project的linker script檔都會把COMMON section放在.bss裡頭。這樣當程式啟動時可以一併把COMMON也清為0。
.bss { *(.bss) *(COMMON) }

2011年11月30日 星期三

Linux和Windows的換行字元

Linux和Windows所使用的換行字元是不一樣的
在Windows底下是使用\r\n (0x0D 0x0A)
在Linux底下是使用\n (0x0A)

因此如果你把Windows上建立的文件搬到Linux上用editor看的話
會在每一行的最後面看到一個 '^M' 的字元
這是因為Linux的換行字元只有\n,因此會把\r給印出來

這點可以做個簡單的實驗來觀察
sway:~/tmp$ ls
hello_linux  hello_windows.txt
hello_linux是在Linux產生出來的檔案
hello_windows.txt是在Windows產生出來的檔案
之後我們用od把ascii dump出來看
sway:~/tmp$ od -c hello_windows.txt
0000000   h   e   l   l   o  \r  \n
0000007

sway:~/tmp$ od -c hello_linux
0000000   h   e   l   l   o  \n
0000006
可以發現Linux下是使用\n當換行字元
而Windows是使用\r\n

這些多出來的'^M'有時後會造成程式的錯誤
當我們把檔案從Windows搬到Linux底下有沒有什麽解決方法呢?
有一些方法,解決方法如下:

1.
dos2unix [-kn] filename [new filename]
sway:~/tmp$ dos2unix -k -n hello_windows.txt new_hello_windows
2. 在Vim下輸入指令,用Regular Expression把\r取代掉
 :%s/\r//g  
3. 用tr把\r給清掉
cat filename | tr -d '\r' > new filename
sway:~/tmp$ cat hello_windows.txt | tr -d '\r' > new_hello_windows

2011年10月31日 星期一

svn+ssh不用輸入密碼

最近碰到一個問題,我在build project時會需要到svn update,因此在build的時候需要一直打密碼,十分的不方便。 還好svn+ssh也可以和ssh一樣建立private key和public key做認證,步驟如下:

1. 在自己的主機上建立private key和public key
sway:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/sway/.ssh/id_rsa):  (按Enter即可)
Enter passphrase (empty for no passphrase): (按Enter即可)
Enter same passphrase again: (按Enter即可)
會在~/.ssh 產生兩個檔案,其中id_rsa.pub是public key,id_rsa是private key

2. 修改.ssh目錄權限
sway:~$ chmod 700 ~/.ssh

3. 複製public key到要登入的server上的~/.ssh/authorized_keys
sway:~$ cd ~/.ssh
sway:~/.ssh$ ls
id_rsa  id_rsa.pub 
sway:~/.ssh$ scp id_rsa.pub sway@server:/home/sway/.ssh/authorized_keys
sway@server's password:
id_rsa.pub 100% 1675 2.5MB/s 00:00
把public key傳上去之後,使用svn+ssh就不需要再輸入密碼了!

2011年10月27日 星期四

Executable and Linkable Format(ELF)


Object file: Compiler編譯原始碼產生的檔案

目前常見的可執行檔格式 :
      Windows下的PE(Portable Executable)
      Linux的ELF(Executable Linkable Format)

Object file就是原始碼編譯過還沒Link的檔案(Windows的.obj和Linux的.o)
廣義來說,Object file和可執行檔的格式其實幾乎是一樣的

Object file被分成好幾個塊Section

    Code Section:常見的名稱有.code或是.data
                 用來存放source code編譯過後產生的instruction

    Data Section:global variable和local static variable都存放在這邊
                 (注意: 不包含local variable以及dynamic variable
                        前者放在程式被load到memory後配置的stack
                        後者放在程式被load到memory後配置的heap  )

                 Data Section又分成三個部分

                 1. .data: 放置已初始化的global variable
                    以及static local variable

                 2. .bss:  放置未初始化的global variable
                    以及static local variable,因為他們的值都是零
                    不需要花額外空間存放零這個值。bss只是為未初始化
                    的變數預留位置而已

                 3. .rodata: 放置Read-Only data,比如說加const修飾過
                    的變數,或是字串常數。ex: printf("%d", a);的%d
                    char a[] = "abcd"; 中的abcd

 
  ELF layout(http://en.wikipedia.org/wiki/File:Elf-layout--en.svg)

這邊出一個小問題,請問一下code中 x1和x2會被放在什麼區段中
static int x1 = 0;
static int x2 = 1;
x1會放在.bss中,x2會放在.data中。x1可以被當作未初始化,compiler最佳化之後
會把x1放到.bss中,節省空間,因為.bss不佔空間。另一個變數x2因為已經初始化
為1,所以放在.data區段中。

除了上述這些比較常用的區段外,ELF還有很多預設的區段。甚至我們可以自己定義區段
比如說我們可以在ELF檔中加入一個"music"的區段,裡面存放一首MP3音樂
程式執行時候可以直接讀取這個區段的MP3資料,把資料加入object file
可以使用objcopy這個指令

ELF檔一開始會有個ELF Header,Header最開頭是ELF的magic number
當OS load可執行檔的時候,會去檢查magic number是否正確
如果錯誤就拒絕載入。Header檔接下來包含一些檔案的資訊
比如說是Little-Endian還是Big-Endian、程式開始執行的的進入點
在unix下,可使用readelf來詳細查看ELF檔案

ELF Header有個e_shoff會定義Section Header Table在ELF檔中的位置
Section Header Table明確的定義這個ELF檔各個區段的資訊
內容包含區段的名稱、所佔的長度、在檔案中的位置、以及讀寫權限等屬性

同樣的,可以使用"readelf -S"來查看Section Header Table的內容

ELF檔還有一些重要的Table如下:

    Relocation Table: Linker在Link object file檔的時候會需要對
                      object file的某些地方作relocation
                      這時候會參考此表進行relocation

    Symbol Table: 儲存所有用到的variable以及function的名稱
                  可以使用nm列出Object file的Symbol Table
                  其中包含Sysmbol所在區段、以及所在位置等資訊

2011年10月6日 星期四

System Call

System Call簡介
System call 是 process 與OS之間的介面,由Linux kernel實做出來給user使用,system call提供user
programm和os溝通的界面,當user program需要os的服務時,user program便使用system call


System Call 流程圖




流程:
system call會伴隨一個trap(在Linux下會跳到int 0x80),此時系統將mode bit由user mode改成
monitor mode(1->0)並查尋trap vector找尋相對應trap service routine
(此時可做context switch 0->1)
執行完此routine發出interrupt告訴os已經完成




User program 與 wrapper routine 是 user space 的 code
system call handler 與 service routine 則是屬於 kernel space
從 User space 切換到 kernel space 是透過中斷
底下的用link這個system call當例子,解釋程式碼

syscall_table.S
PATH : /usr/src/linux/arch/i386/kernel/syscall_table.S
在 Linux 中, 每個 system call 都有自己獨有的號碼。
當 user-space 執行一個 system call 時,process是去參考 syscall 的號碼而不是名字。



unistd.h
PATH : /usr/src/linux/include/asm/unistd.h
unistd.h 是一個重要的標頭檔,裡頭是 system call 編號的定義,當 system call 發生時,system call 的號碼將透過 register (EAX) 傳給 kernel。





#define _syscall2(type,name,type1,arg1,type2,arg2) 
type name(type1 arg1,type2 arg2) 
{ 
long __res; 
__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" 
         : "=a" (__res) 
         : "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)) 
         : "memory"); 
__syscall_return(type,__res); 
}

unistd.h 也定義了不同參數的 system call handler,上面的程式碼是處理 2 個參數的 handler
這是一個 macro,當遇到系統呼叫的時候,就會被展開。

參數傳遞與傳回值
system call 的編號透過 %eax 暫存器來指定;若要傳遞參數,則是透過其它暫存器來傳遞(最上面的流程圖上有標示)
Linux system call 最多可傳遞6個參數,參數的傳遞是透過以下的暫存器來完成:
%ebx:第1個參數。
%ecx:第2個參數。
%edx:第3個參數。
%esi:第4個參數。
%edi:第5個參數。
%ebp:第6個參數(做臨時用途)。

x86 的 Interrupt
x86 的interrupt(中斷)可分為系統定義與使用者自訂:
中斷向量0~8、10~14、16~18:predefined interrupts and exceptions。
中斷向量19-31:保留。
中斷向量32-255:user-defined interrupts(maskable interrupts)。

當從 shell 執行 link 時,0x80 號中斷向量會指到 system_call 進入點的位址,由於 link 有兩個參數,const char* oldfile 和 const char* newfile,因此 shell 會執行 syscall2(int link, const char* oldname, const char* newname),執行 sys_link(),sys_link()會呼叫linkat()。在 linux 中,目錄和檔案在系統中被視為同樣。

int link(const char* oldname, const char* newname)
{
    long _res;
    _asm_  volatile(“int $0x80”
         :”=a”(_res)
         :”0”(_NR_link),”b”((long)(oldname)),
         “c”((long)(newname)));
    do{
        if((unsigned long)(_res)>=(unsigned long)(-(128+1))){
            errno=-(_res);
            _res=-1;
    }
    return (int)(_res);
    }while(0);
}

下面這段組語是 system call 的進入點,也就是 system call table
PATH : /usr/src/linux/arch/i386/kernel/entry.S
ENTRY(system_call)
        pushl %eax                      # save orig_eax
        SAVE_ALL
        GET_CURRENT(%ebx)
        testb $0x02,tsk_ptrace(%ebx)    # PT_TRACESYS
        jne tracesys
        cmpl $(NR_syscalls),%eax
        jae badsys
        call *SYMBOL_NAME(sys_call_table)(,%eax,4)
        movl %eax,EAX(%esp)             # save the return value
 ENTRY(ret_from_sys_call)
        cli                             # need_resched and signals atomic test
        cmpl $0,need_resched(%ebx)
        jne reschedule
        cmpl $0,sigpending(%ebx)
        jne signal_return

syscalls.h
PATH : /usr/src/linux/include/linux/syscalls.h
這個檔案包含了 system call 的宣告。





asmlinkage 是在i386 system call實做中,gcc 很重要的一個標籤。他是一個macro,會被展開成
#define asmlinkage __attribute__((regparm(0)))
這是/usr/include/asm/linkage.h 裡頭的定義,regparm(0)表示不使用register傳遞參數
如此一來所有的參數就會被強迫放在stack當中

這麼做的原因是因為system call handler是assembly code,但是system call routine是C code
為了要保證當system call handler呼叫相對應的system call routine時,符合C語言參數傳遞的規則
是以 stack 方式傳參數,在C function的 prototype前面就要加上 "asmlinkage"。

System call 結束
當 system call 執行完的時候,最後會執行 ret_from_sys_call() 離開。
最後回到 syscallX() 中去。在 syscallX() 中,檢測是否有錯誤碼,然後返回。

2011年10月5日 星期三

GNU Screen教學

最近發現一個Linux底下好用的軟體叫Screen,他可以在一個實體的終端機中模擬多個虛擬視窗
當你要看一堆code的時候,就會覺得他很方便

安裝好後輸入screen就可以開始執行
Screen使用上都是由一個前置鍵【Ctrl-A】所組成,以下是一些我覺得常用的指令

【Ctrl-A】【c】 : 建立一個新的視窗
【Ctrl-A】【w】: 查看目前已經存在的視窗清單
【Ctrl-A】【0~9】 : 切換不同編號的視窗
【Ctrl-A】【Ctrl-A】: 切換回上一個視窗,就是遙控器來回切換的功能
【Ctrl-A】【K】: 刪除這個視窗
【Ctrl-A】【\】: 刪除所有視窗,並且關閉Screen
【Ctrl-A】【g】: 取消畫面閃動提示
【Ctrl-A】【n】: 切換到下一個視窗
【Ctrl-A】【p】: 切換到上一個視窗
底下是Screen指令大全:

Key Action Notes
Ctrl+a c new window
Ctrl+a n next window
Ctrl+a p previous window
Ctrl+a “ select window from list
Ctrl+a Ctrl+a previous window viewed
Ctrl+a S split terminal horizontally into regions Ctrl+a c to create new window there
Ctrl+a | split terminal vertically into regions Requires debian/ubuntu patched screen 4.0
Ctrl+a :resize resize region
Ctrl+a :fit fit screen size to new terminal size Ctrl+a F is the same. Do after resizing xterm
Ctrl+a :remove remove region Ctrl+a X is the same
Ctrl+a tab Move to next region
Ctrl+a d detach screen from terminal Start screen with -r option to reattach
Ctrl+a A set window title
Ctrl+a x lock session Enter user password to unlock
Ctrl+a [ enter scrollback/copy mode Enter to start and end copy region.
Ctrl+a ] to leave this mod
Ctrl+a ] paste buffer Supports pasting between windows
Ctrl+a > write paste buffer to file useful for copying between screens
Ctrl+a < read paste buffer from file useful for pasting between screens
Ctrl+a ? show key bindings/command names Note unbound commands only in man page
Ctrl+a : goto screen command prompt up shows last command entered