為Android應用程式讀取/dev下裝置而提權(二) 在為Android應用程式讀取/dev下裝置而提權(一)中,簡單總結了提權的兩種方法:
device_init和
init.rc 。在此篇文章中,我將詳細總結的是稍一不留神,就容易把人弄暈乎的
init.c、
device_init和
init.rc 三者之間的關係,TA們到底是如何工作的。
目錄結構 ls一下system/core/init/
devices.c、devices.h、
init.c、init.h、keywords.h、parser.c、property_service.c.... 另外system/core/rootdir/
init.rc ,當然init.rc的位置可以另行指定。
init流程 init過程的起點是
init.c : *注釋中的序號表示執行順序
int main(int argc, char **argv){ … … mkdir("/dev", 0755); //建立基本檔案系統節點 mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL);… … INFO("reading config file\n"); parse_config_file("/init.rc"); // 1、 調用parse_config 函數解析init.rc指令碼 //11、經過解析,init.rc的內容就被分為多少個段,被串在action_list鏈表中。on 開頭的都是action類型的段,比如init段,init段用一個結構體struct action表示, 其中name是init,所有這個段內的命令,都被串在commands鏈表中。 action_for_each_trigger("early-init", action_add_queue_tail); //12、 遍曆action_list鏈表,尋找name是early-init的那個action,將這個節點放在action_queu e的尾部。 drain_action_queue(); // 13、將action_queue尾部的節點遍曆,然後刪除。就相當於遍曆name是early-init的action節點內的commands鏈表。就是在執行init.rc指令碼中 on early-init段內的所有命令。 … … INFO("device init\n"); device_fd = device_init(); //常見必要的裝置節點 property_init(); //init 以後的任務就是proper_service action_for_each_trigger("init", action_add_queue_tail); //14、將init 段,加入action_queue drain_action_queue(); // 執行init段得命令 … …}
system/core/init/parser.c:
static void parse_config(const char *fn, char *s){ struct parse_state state; char *args[MAXARGS]; int nargs; nargs = 0; state.filename = fn; state.line = 1; state.ptr = s; state.nexttoken = 0; state.parse_line = parse_line_no_op; //這個函數是空的,就是什麼都不做 for (;;) { switch (next_token(&state)) { // 2、 和T_TEXT狀態配合,先把把每一行的參數都放在args數組裡 case T_EOF: state.parse_line(&state, 0, 0); // 最後看這,到此檔案解析完成,也是上一段的完成,需要寫個NULL表示末尾。 return; case T_NEWLINE: if (nargs) { int kw = lookup_keyword(args[0]); // 3、得到新的一行,開始解析,判斷一下拿到的第一個參數是什麼關鍵字,這裡面有幾種情,命令COMMAND,段SECTION,和選項OPTION,這個選項是針對服務的,開啟,關閉等操作。 if (kw_is(kw, SECTION)) { // 4、判斷得到的關鍵字是不是段,keywords.h裡定義了各種能解析的關鍵字分別是什麼屬性。 state.parse_line(&state, 0, 0); // 表示上一段解析結束,因為使用的是雙向鏈表,這樣就給鏈表最後一個元素寫NULL,表示到末尾了。 parse_new_section(&state, kw, nargs, args); // 5、建立一個新段的鏈表,比如init段,先跳到這個函數看,然後再回來。 } else { state.parse_line(&state, nargs, args); //10、 得到新的一行,通過上面的操作已經知道現在是在什麼段中,是on 還是service,行解析函數也做了相應變化,開始解析這一行,加入action的commands鏈表中。 } nargs = 0; } break; case T_TEXT: if (nargs < MAXARGS) { args[nargs++] = state.text; } break; } }}void parse_new_section(struct parse_state *state, int kw, int nargs, char **args){ printf("[ %s %s ]\n", args[0], nargs > 1 ? args[1] : ""); switch(kw) { // 6、這裡判斷 是什麼類型的段,不同類型的段使用的解析函數不同,說白了就是分命令還是服務。 case K_service: state->context = parse_service(state, nargs, args); if (state->context) { state->parse_line = parse_line_service; return; } break; case K_on: state->context = parse_action(state, nargs, args); // 7、建立一個action 鏈表,把這個鏈表加入到action_list中 if (state->context) { state->parse_line = parse_line_action; // 8、把行解析函數換掉,原來是parse_no_op 什麼都不做,再在要把每行都解析成一個命令動作。把這個命令動作加入到action中的commands鏈表內 return; } break; } state->parse_line = parse_line_no_op; // 9、走到這就是出錯了,段的名字沒寫或者寫多了}
本章小結
經過上面的分析,對/dev/裝置許可權的修改放在不同的位置會有覆蓋的效果,device.c內的修改會覆蓋early-init段內的命令,init 段內的命令會覆蓋device.c中的修改,如果3個位置都有對用一個裝置許可權的修改,那init段的修改會最終生效。