Showing posts with label linux. Show all posts
Showing posts with label linux. Show all posts
Tuesday, October 18, 2011
Friday, September 2, 2011
Exim mail config on Redmine
production:
delivery_method: :smtp
smtp_settings:
address: 127.0.0.1
port: 25
domain: redmine.example.com
authentication: :none
Tuesday, July 26, 2011
Sunday, July 24, 2011
Friday, July 22, 2011
Thursday, July 21, 2011
在Debian中安裝桌面環境 X-Window | 電腦茶包 Blog
在Debian中安裝桌面環境 X-Window | 電腦茶包 Blog
在 Debian 中安裝套件是一件很容易的事情
因為 apt-get 會幫你搞定一切,安裝軟體輕鬆又快樂
如果一開始只有裝基本系統,後來想加入桌面環境時也是很簡單
在 Console 中執行這行指令,桌面系統就裝好了。夠簡單吧
apt-get install xorg gnome
接下來要安裝中文的修正套件
apt-get install ttf-arphic-uming scim-tables-zh im-switch
安裝完成後再執行 startx 就能進去桌面環境了
如果嫌 gnome 太肥,你也可以改用 kde或 lxde
桌面套件 | 套件容量 | 指令 |
x-window + gnome | 1597 MB | apt-get install xorg gnome |
x-window + kde | 1116 MB | apt-get install xorg kde |
x-window + lxde | 288 MB | apt-get install xorg lxde |
Thursday, July 14, 2011
Sunday, July 10, 2011
Tuesday, June 21, 2011
Tuesday, June 14, 2011
Wednesday, June 8, 2011
Wednesday, May 18, 2011
Tuesday, April 19, 2011
Thursday, April 14, 2011
Jserv's blog: 親手打造 Dynamic Library Loader
From Jserv's blog: 親手打造 Dynamic Library Loader
這幾個月又繼續設計 / 實做新的 Kernel (與相關的系統程式),貫徹「每年練習寫一個作業系統」的小目標,其中對 dynamic linker 的支援,是重要的特徵,本文則探討如何在 GNU/Linux 實做出 Dynamic Linker / Dynamic Library Loader (即 ld.so 與 libdl.so) 的功能,並以 ELF 執行檔格式作為探討對象,如此的概念可應用於 RTOS 與廣泛的嵌入式系統。
許多程式設計師都知道 dynamic linker,也知曉像是 LD_PRELOAD 的機制,但鮮少人真正瞭解其背後的內部工作原理,因為難題不僅是 linker 與 loader 的行為,而是在執行時期 (Runtime),要有種機制得以確保 dynamic linked 的程序中的函式 / 符號位址,已正確地指向了動態函式庫 (以下簡稱 "DLL" 或 DSO [Dynamic Shared object,UNIX 術語],本文不特別強調其分野) 裡頭的對應位址,基於如此考量,系統至少該具備以下的特徵:
在原始程式 ndl.c 中,我們引入 libelf 所提供的 bfd.h 檔頭 (BFD, the Binary File Descriptor library),並宣告兩個結構體,供後續使用:
由 jserv 發表於 November 13, 2009 10:14 AM
許多程式設計師都知道 dynamic linker,也知曉像是 LD_PRELOAD 的機制,但鮮少人真正瞭解其背後的內部工作原理,因為難題不僅是 linker 與 loader 的行為,而是在執行時期 (Runtime),要有種機制得以確保 dynamic linked 的程序中的函式 / 符號位址,已正確地指向了動態函式庫 (以下簡稱 "DLL" 或 DSO [Dynamic Shared object,UNIX 術語],本文不特別強調其分野) 裡頭的對應位址,基於如此考量,系統至少該具備以下的特徵:
- PIC (Position Independent Code) : 簡單來說,DLL/DSO 本質上要能夠被載入到記憶體的任何有效的位址,也就是字面上 "Position Independent" 的意涵。而早期 UNIX 的 a.out 執行檔格式其實不是不能作 dynamic linker/loader,是被約束於 Position Dependent,也就是一定要被載入到特定的記憶體位址,才能運作,很沒有彈性,USL (UNIX SYSTEM Laboratories) 後來發展的 ELF 格式 (Executable and Linking Format) 就破除如此的限制。編譯器支援 PIC 的特徵 (gcc 的編譯選項是 "-fpic" / "-fPIC" ),會使輸出的 object code 與記憶體位址無關,並減少對絕對位址的使用。
- 在執行時期才去處理符號 (變數與函式等):透過 ELF 裡頭的 symtab (symbol table) 與 relocation 機制去達成,也就是說,載入 DLL 的那一刻,其實無法執行程式本體,需要在解析 symtab 與對所有的位址都 relocation 後,才會真正去執行,而配合前述的 PIC,可大幅降低執行時期的開銷。當然,沒有 PIC,還是能做出 DLL/DSO,不過 relocation 的開銷就會相當可觀。
- GOT (Global Offset Table) 的引入:要達到可用的 PIC,需要一份全域的 GOT,讓編譯器輸出的機械碼中,保留一個暫存器 (register) 去參照 GOT 這個排列好指向 symbol 位址的表格,如此一來,DLL/DSO 載入後,只需要一次的 relocation 即可得到全域的位址 (以 UNIX Process 的觀點)
- dlopen()
- dlsym()
- dlclose()
- 透過 dlopen() 將一個 ELF object code 載入並映射到記憶體,注意:為了簡化設計的難度,我們的實做將忽略 PIC/GOT
- 透過 dlsym() 將稍早載入的 object code 中提取特定 symbol 的進入點 (UNIX Process 的函式位址),當然,這是做了 relocation 之後的位址
- 傳遞必要的參數給指向前述位址的 function pointer,嘗試去執行,驗證其功能是否符合預期
- 將 symbol 進入點作記憶體 dump,觀察其機械碼的排列方式
- 以 dlclose() 將必要的資源釋放
#include上述程式包含兩輪的測試,一個是載入 'add.o',另一個是 'hello.o',前者是簡單的算術操作,而後者涉及函式呼叫。對應的 DLL/DSO 程式碼列表:#include "ndl.h" /* dump machine code of loaded DSO */ static void dump(char *p); int main() { void *handle; /* add */ handle = ndlopen("add.o"); int (*fp_add) (int, int) = ndlsym(handle, "add"); printf("add (%p):\n", fp_add); dump((char*) fp_add); printf("[add] 1 + 1 = %d\n", fp_add(1, 1)); ndlclose(handle); printf("\n"); /* hello */ handle = ndlopen("hello.o"); void (*fp_hello) (char *) = ndlsym(handle, "hello"); char *msg = ndlsym(handle, "dyn_str"); printf("hello (%p):\n", fp_hello); dump((char *) fp_hello); fp_hello("Hello World"); fp_hello(msg); ndlclose(handle); return 0; } static void dump(char *p) { int c = 0; while (*p != (char) 0xc3) { /* 'c3' = asm("ret") */ printf("%02x ", (*p++) & 0xff); if (++c % 16 == 0) printf("\n"); } printf("c3\n"); }
jserv@venux:/tmp/ndl$ cat add.c int add(int x, int y) { return x + y; } jserv@venux:/tmp/ndl$ cat hello.c #include以下是參考的編譯與執行輸出:char dyn_str[] = "__DSO__"; void hello(char *s) { printf("[hello] %s\n", s); }
jserv@venux:/tmp/ndl$ make gcc -c hello.c -Os -Wall -fomit-frame-pointer -I./external gcc -c add.c -Os -Wall -fomit-frame-pointer -I./external gcc -o test_ndl test_ndl.c ndl.c dummy.c \ /usr/lib/libbfd.a /usr/lib/libiberty.a -static \ -Os -Wall -fomit-frame-pointer -I./external -Wl,-O1 jserv@venux:/tmp/ndl$ ./test_ndl :: handle = 0x8e026a8 add (0x8e02854): 8b 44 24 08 03 44 24 04 c3 [add] 1 + 1 = 2 :: reloc_name = __printf_chk :: handle = 0x8e02770 hello (0x8e029bc): 83 ec 10 ff 74 24 14 68 dc 29 e0 08 6a 01 e8 51 a1 2f ff 83 c4 1c c3 [hello] Hello World [hello] __DSO__那麼上述的實驗中,有哪些該注意的細節呢?在深入探討筆者提出的 ndl 前,可以留意到:
- 首先,'test_ndl' 這個程式本身是 statically linked,連結到 libbfd 與 libiberty 這兩個專門處理 ELF 的函式庫 (在 Debian/Ubuntu 裡頭,由套件 binutils-dev 所提供),並無連結到 libdl,而是採用我們親手打造的函式
- 一般 statically linked 的程式無法使用 dlopen(),但我們的程式沒有如此限制,仍可在動態時期載入 DSO 並處理 symbol 與 relocation
- 迴避 PIC/GOT 的細節,編譯參數沒有 -fpic 或 -fPIC
- 'add.o' 與 'hello.o' 的差別在於,'hello.c' 有呼叫到 printf() 的動作,這致使執行時期仍需要多作一個 relocation,此動作需要在實際呼叫被載入的 hello() 前準備好,否則無法運作
jserv@venux:/tmp/ndl$ readelf -r add.o There are no relocations in this file. jserv@venux:/tmp/ndl$ readelf -r hello.o Relocation section '.rel.text' at offset 0x368 contains 2 entries: Offset Info Type Sym.Value Sym. Name 00000008 00000501 R_386_32 00000000 .rodata.str1.1 0000000f 00000902 R_386_PC32 00000000 __printf_chk由此可見,'hello.o' 所呼叫的 printf() 函式主體其實位於 statically-linked 的 'test-ndl' 執行檔中,而 printf() 的符號在編譯時期被替換為 '__printf_chk'。readelf 工具程式告訴我們,'hello.o' 在 Relocation section '.rel.text' 有兩個符號,對照於 'hello.c':
- '.rodata.str1.1' 即 "[hello] %s\n" (傳遞給 printf() 的參數),型態為 R_386_32 (absolute 32-bit)
- '__printf_chk' 即 printf(),其型態為 R_386_PC32 (PC relative 32 bit signed)
jserv@venux:/tmp/ndl$ objdump -xd hello.o hello.o: file format elf32-i386 hello.o architecture: i386, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x00000000 ... Disassembly of section .text: 00000000對照於 test_ndl 的輸出:: 0: 83 ec 10 sub $0x10,%esp 3: ff 74 24 14 pushl 0x14(%esp) 7: 68 00 00 00 00 push $0x0 8: R_386_32 .rodata.str1.1 c: 6a 01 push $0x1 e: e8 fc ff ff ff call f f: R_386_PC32 __printf_chk 13: 83 c4 1c add $0x1c,%esp 16: c3 ret
hello (0x8e029bc): 83 ec 10 ff 74 24 14 68 dc 29 e0 08 6a 01 e8 51 a1 2f ff 83 c4 1c c3就再清楚不過了,'hello' symbol 的反組譯自 '83' 'ec' 10' (sub $0x10,%esp) 開始到 c3 (ret) 為止,都被載入到記憶體,並做了必要的 relocation。以下是簡要探討 ndlopen(), ndlsym(), ndlclose() 的實做,詳情可參閱原始程式碼 [ndl.tar.bz2]。
在原始程式 ndl.c 中,我們引入 libelf 所提供的 bfd.h 檔頭 (BFD, the Binary File Descriptor library),並宣告兩個結構體,供後續使用:
typedef struct { const char *name; void *fp; } ndl_sym_t; typedef struct { htab_t syms; char *map; size_t length; } ndl_t;在 API 的實做則是:
void *ndlsym(void *h, const char *symbol) { ndl_t *handle = (ndl_t *) h; ndl_sym_t **sym; void *addr; sym = (ndl_sym_t **) htab_find_slot(handle->syms, symbol, NO_INSERT); if (! sym) return NULL; addr = (*sym)->fp; mprotect((void *)((((int) addr + 4095) & ~4095) - 4096), 4096, PROT_READ | PROT_WRITE | PROT_EXEC); return addr; } void ndlclose(void *h) { ndl_t *handle = (ndl_t *) h; free(handle->map); }ndlsym() 與 ndlclose() 的實做就相當顯然了,只要 dlopen() 能將 ELF 必要的欄位與資訊填入前述的資料結構,那麼就是作必要的查表動作即可,需要留意的是 mprotect() 的呼叫,因為我們要將對照後的記憶體位址區段標示為「可讀、可寫、可執行」(x86 的特性)。dlopen() 的實做稍微複雜一點,不過重點是實做 load_relocs() 函式,其接受指向 ndl_t 結構的 handle,以及指向已開啟 ELF object code 的 bfd 結構的 abfd 兩個參數。程式碼列表如下:
static int load_relocs(ndl_t *handle, bfd *abfd) { int size, i; asection *sect = bfd_get_section_by_name(abfd, ".text"); arelent **loc; size = bfd_get_reloc_upper_bound(abfd, sect); if (size < 0) { bfd_perror("bfd_get_reloc_upper_bound"); return 1; } loc = (arelent **) malloc(size); size = bfd_canonicalize_reloc(abfd, sect, loc, g_syms); if (size < 0) { bfd_perror("bfd_canonicalize_reloc"); return 1; } for (i = 0; i < size; i++) { arelent* rel = loc[i]; void **p = (void **) (handle->map + sect->filepos + rel->address); const char *name; if (!rel->sym_ptr_ptr || !*(rel->sym_ptr_ptr)) continue; name = (*(rel->sym_ptr_ptr))->name; if (!name || !name[0]) continue; /* section */ if (name[0] == '.') { asection* s = bfd_get_section_by_name(abfd, name); *p = handle->map + (int)*p + s->filepos + rel->addend; } /* function */ else { *p = lookup_func_table(name, p); printf("\t:: reloc_name = %s\n", name); } } free(loc); return 0; }回顧筆者提到 Relocation section '.rel.text' 有兩個符號:'.rodata.str1.1' 與 '__printf_chk',前者以 '.' (句點) 開頭者,為 section,否則為 function,再回顧 objdump 的輸出:
jserv@venux:/tmp/ndl$ objdump -xd hello.o ... SYMBOL TABLE: 00000000 l df *ABS* 00000000 hello.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .rodata.str1.1 00000000 .rodata.str1.1 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g F .text 00000017 hello 00000000 *UND* 00000000 __printf_chk 00000000 g O .data 00000008 dyn_str ...而作為一個 dynamic library loader,ndl 的工作就是基於這兩項,做出正確的查詢動作,以 BFD 提供的函式,將正確的位址找出。有趣的是,既然 printf() 在編譯時期被轉換為 '__printf_chk' 這個 symbol,而 'hello.o' 本身卻只有 undefined symbol (即上列 objdump 輸出的 '*UND*'),其實做在哪呢?就在 test_ndl.c 中,在編譯為 statically-linked 程式時,gcc 默默的將一份 '__printf_chk' 實做碼 (來自 GNU glibc) 連結到 test_ndl 這個應用程式。我們的 load_relocs() 中,針對函式的查詢則用輕便的方式:窮舉法,以下是原始程式碼:
static inline void *lookup_func_table(const char *func_name, void **ptr) { if (0 == strcmp(func_name, "printf")) *ptr = (void *)((unsigned int) &printf - (unsigned int) ptr - 4); else if (0 == strcmp(func_name, "puts")) *ptr = (void *)((unsigned int) &puts - (unsigned int) ptr - 4); else if (0 == strcmp(func_name, "__printf_chk")) *ptr = (void *)((unsigned int) &__printf_chk - (unsigned int) ptr - 4); else { /* FIXME: handle uncaught function entries */ } return *ptr; }當然,筆者這麼作,實在是相當偷懶,但對一個 self-contained 的環境來說,應已足夠,需要留意的是 frame pointer 的操作,所以適度要調整進入點的位置:"ptr - 4"。正如前述所及,完整的 dynamic linker/loader 需要考慮相當多細節,但本文用最簡便的方式,提供可行且易於分析的途徑,未來筆者會再探討涉及作業系統與函式庫的議題。
由 jserv 發表於 November 13, 2009 10:14 AM
Wednesday, April 13, 2011
Tuesday, March 15, 2011
Sunday, March 6, 2011
Subscribe to:
Posts (Atom)