三易阳、嵌入式之虛擬機分析 (1) rt-thread的shell


1 簡介

1.1 說明

little c 解釋器
http://blog.csdn.net/redraiment/article/details/4693952
編譯器:https://www.amobbs.com/thread-5536737-1-1.html

這里面所說的虛擬機,其實也算是元編程的一種實現(xiàn)方式。

目前的虛擬機有以下幾種:
CLR, JVM, Parrot, LLVM。

要在小型單片機上運行編譯器痹升,我暫時想到兩個辦法:
(1)如果程序空間足夠裝下編譯器代碼,但內(nèi)存不夠畦韭,可以利用SD卡疼蛾,將asmcode[]、mcode[]等存入SD卡(或其它存儲設(shè)備)艺配。
(2)如果程序空間只能裝下虛擬機的代碼察郁,則可以將這個編譯器的C代碼用“簡單C” 的語法重新寫一下,(在電腦上)編譯成機器碼存入SD卡中转唉,讓單片機運行虛擬機皮钠,運行SD卡中的代碼,就是說在虛擬機中運行編譯器酝掩;

比較值得有參考價值的虛擬機分析:參考鏈接

而標(biāo)準(zhǔn)規(guī)范的java虛擬機文檔:鏈接

1.2 名詞

1.2.1 前綴表達(dá)式

也稱為前綴記法鳞芙、波蘭式眷柔。稱作波蘭式是為了紀(jì)念其發(fā)明者波蘭數(shù)學(xué)家Jan Lukasiewicz期虾。

1.2.2 中綴表達(dá)式

同類型的還有前綴表達(dá)式,后綴表達(dá)式驯嘱。

中綴表達(dá)式是指運算符在運算數(shù)的中間镶苞,計算中綴表達(dá)式時需要用到兩個棧:數(shù)字棧和運算符棧。在整個中綴表達(dá)式求值的過程中主要涉及到的模塊有:棧的相關(guān)操作鞠评、優(yōu)先級表的確立茂蚓、輸入的待計算字符串拆分為數(shù)字和運算符以及運算處理等

有一個比較不錯的寫法,鏈接

1.2.3 后綴表達(dá)式

后綴表達(dá)式(又稱為逆波蘭reverse polish)就是不需要括號就可以實現(xiàn)調(diào)整運算順序的一種技巧。

1.2.3 二叉樹

可以通過不同遍歷方式組合成中輟表達(dá)式和后綴表達(dá)式聋涨。
二叉樹定義:每個節(jié)點最多有兩個子樹的樹結(jié)構(gòu)晾浴。

2 rt-thread的finsh shell

先從rt-thead的finsh shell開始研究。
rt-thread的github鏈接地址:下載鏈接

整體代碼邏輯:
在finsh_run_line中調(diào)用以下三個主要函數(shù)牍白,完成大體功能脊凰。

  • finsh_parser_run
    完成xxxx
  • finsh_compiler_run
  • finsh_vm_run

這樣的流程,在我的理解來看茂腥,就是一個 解析器(語法解析器)狸涌,編譯器(解釋器),虛擬機執(zhí)行的過程最岗。

對finsh_parser_run進(jìn)行分析:


  • 若在shell中輸入表達(dá)式: 1 + 1
  • a.執(zhí)行: proc_expr_statement
  • b.執(zhí)行: proc_expr
  • c.執(zhí)行:proc_assign_expr
  • d.執(zhí)行:proc_inclusive_or_expr帕胆,帶有后續(xù)處理
  • e.執(zhí)行:proc_exclusive_or_expr,帶有后續(xù)處理
  • f.執(zhí)行:proc_and_expr般渡,有后續(xù)處理
  • g.執(zhí)行:proc_shift_expr懒豹,有后續(xù)處理
  • h.執(zhí)行:proc_additive_expr,有后續(xù)處理
  • i.執(zhí)行:proc_multiplicative_expr诊杆,有后續(xù)處理
  • j.執(zhí)行:proc_cast_expr歼捐,有后續(xù)處理
  • k.執(zhí)行:proc_unary_expr
  • l.執(zhí)行:proc_postfix_expr,調(diào)用該函數(shù)之前調(diào)用了finsh_token_replay晨汹。
  • m.執(zhí)行:proc_primary_expr豹储,最終執(zhí)行到底。

執(zhí)行到proc_primary_expr后淘这,能識別出1剥扣。也就是說,執(zhí)行到分支:finsh_token_type_value_int铝穷。同時往數(shù)組global_node_table添加一個finsh_node钠怯。

然后:

  1. 返回到proc_postfix_expr,其中將該函數(shù)返回值賦值給struct finsh_node* postfix;該值應(yīng)該就是前面識別出來的結(jié)點1曙聂。隨后調(diào)用next_token晦炊,經(jīng)過一系列處理,token的值為:finsh_token_type_add宁脊。然后該函數(shù)仍舊執(zhí)行了finsh_token_replay断国,返回了上面提到的postfix。
  2. 返回到proc_unary_expr榆苞,仍舊繼續(xù)返回稳衬。返回到,proc_cast_expr坐漏。
  3. proc_cast_expr后薄疚,返回的仍然是1的結(jié)點碧信。
  4. 后面一直返回到proc_additive_expr,其調(diào)用完成proc_multiplicative_expr后街夭,執(zhí)行next_token砰碴,而token的值為finsh_token_type_add。mul是1的結(jié)點板丽。mul_new返回值為 proc_multiplicative_expr的結(jié)果衣式。這一次調(diào)用proc_multiplicative_expr,執(zhí)行到proc_cast_expr檐什,搜尋next_token碴卧,這個時候找到另外一個1結(jié)點。最終mul_new就是另外一個1結(jié)點了乃正。這個結(jié)點就是global_node_table創(chuàng)建的第二個值了住册。
  5. 返回到proc_additive_expr,執(zhí)行make_sys_node瓮具,創(chuàng)建FINSH_NODE_SYS_ADD結(jié)點荧飞。后執(zhí)行賦值,node->child(+)=先找到的結(jié)點1名党,結(jié)點1->sibling=結(jié)點1叹阔。最后返回FINSH_NODE_SYS_ADD結(jié)點。
  6. 最后返回到self->root = node;那么self->root就是前面提到的FINSH_NODE_SYS_ADD結(jié)點传睹。

  • 對finsh_parser_run進(jìn)行分析:

  • a.執(zhí)行finsh_type_check
  • b.清除text_segment耳幢,finsh_vm_stack。設(shè)置finsh_compile_sp欧啤,finsh_compile_pc睛藻。
  • c.獲取sibling node,調(diào)用finsh_compile邢隧,傳參數(shù)為root node店印,如果有sibling,則彈出棧sibling node倒慧。

重點在分析finsh_compile上:

  • a.判斷node->child是否為空按摘,否則編譯child結(jié)點。這里使用的遞歸的操作方法纫谅。
  • b.判斷node->node_type炫贤,最先判斷的child的node_type,為FINSH_NODE_VALUE_INT系宜。接著執(zhí)行照激,
finsh_code_byte(FINSH_OP_LD_DWORD);
finsh_code_dword(node->value.long_value);
  • c.遞歸獲取到child->sibling結(jié)點发魄。編譯sibling結(jié)點盹牧。
finsh_code_byte(FINSH_OP_LD_DWORD);
finsh_code_dword(node->value.long_value);
  • d.判斷的是root->node_type俩垃,是FINSH_NODE_SYS_ADD。執(zhí)行:
finsh_code_byte(FINSH_OP_ADD_BYTE);

從這里看汰寓,仍然是一種后綴表達(dá)式的形式口柳。

  • 對finsh_vm_run進(jìn)行分析:

    finsh_sp = &finsh_vm_stack[0];

    /* set pc to the beginning of text segment */
    finsh_pc = &text_segment[0];

    while ((finsh_pc - &text_segment[0] >= 0) &&
        (finsh_pc - &text_segment[0] < FINSH_TEXT_MAX))
    {
        /* get op */
        op = *finsh_pc++;

        /* call op function */
        op_table[op]();
    }

在前面說的compile中,數(shù)據(jù)存儲的內(nèi)容為:
0x24
4字節(jié)數(shù)值有滑,低字節(jié)在前跃闹,高字節(jié)在后。
0x24
4字節(jié)數(shù)值毛好,低字節(jié)在前望艺,高字節(jié)在后。
0x03

所以肌访,虛擬機執(zhí)行的為:

  • a.執(zhí)行OP_ld_dword找默,獲取4字節(jié)數(shù)據(jù),然后放到棧上吼驶。同時pc指針前進(jìn)惩激。
  • b.繼續(xù)執(zhí)行a步驟操作。
  • c.執(zhí)行OP_add_dword蟹演,最終調(diào)用OP_BIN_DWORD风钻,將棧中數(shù)據(jù)進(jìn)行處理,同時出棧指針遞減酒请,也就是出棧數(shù)據(jù)骡技。

自此,按照例子 1 + 1的整個路徑分析完成羞反。

那么哮兰,三個函數(shù)做的事情,總結(jié)如下:

  • finsh_parser_run主要是將字符串苟弛,一個個分拆喝滞,也就是編譯原理中會說的詞法分析。然后形成一棵樹膏秫。
  • finsh_compiler_run主要是將樹中的數(shù)據(jù)提取出來右遭,放到虛擬機中的代碼段中。這里并沒有區(qū)分?jǐn)?shù)據(jù)區(qū)和代碼區(qū)缤削,一股腦的放在一起窘哈。
  • finsh_vm_run主要是將代碼段中的數(shù)據(jù),根據(jù)操作碼亭敢,提取出數(shù)據(jù)滚婉,放到棧中。這里面用到了后綴表達(dá)式帅刀,先提取出兩個數(shù)让腹,然后第三個數(shù)是+远剩,-,*骇窍,/等等的操作碼瓜晤,計算完成后,可以出一個棧的數(shù)據(jù)腹纳,然后繼續(xù)處理痢掠。

接著再來看看輸入一個函數(shù),是怎樣的執(zhí)行流程嘲恍。比如hello():
我先說明一點足画,輸入函數(shù),函數(shù)的參數(shù)個數(shù)佃牛,rt-thread的shell并沒有做檢測锌云,這應(yīng)該是一個失誤,是一個很不好的設(shè)計吁脱。

    1. finsh_parser_run中調(diào)用token_run中桑涎,獲取到current_token為finsh_token_type_identifier。后執(zhí)行到proc_primary_expr兼贡,執(zhí)行finsh_node_new_id()攻冷,會先調(diào)用finsh_var_lookup(),沒找到就調(diào)用finsh_sysvar_lookup()遍希,仍沒找到等曼,就調(diào)用finsh_syscall_lookup(),當(dāng)然按照例子上來看凿蒜,最終是找到了禁谦。最后會調(diào)用finsh_node_allocate(),分配結(jié)點废封,node_type為FINSH_NODE_ID州泊。也就是下面這幾個:
global_node_table[i].node_type = FINSH_NODE_ID;
node->id.syscall = xxx;
node->idtype = FINSH_IDTYPE_SYSCALL;
  1. 執(zhí)行到函數(shù)proc_postfix_expr(),調(diào)用完proc_primary_expr()后漂洋,執(zhí)行next_token()遥皂,進(jìn)入分支finsh_token_type_left_paren,接著就去尋找右括號。我們不看找不到右括號或者帶參數(shù)情況,只看找到了右括號后的執(zhí)行路徑衅疙。
    3.調(diào)用make_sys_node,這個時候样悟,添加結(jié)點FINSH_NODE_SYS_FUNC,并把上面的FINSH_NODE_ID添加進(jìn)來。也就是說窟她,root為FINSH_NODE_SYS_FUNC結(jié)點陈症,child為FINSH_NODE_ID,sibling為空礁苗。
    4.調(diào)用finsh_compile,進(jìn)入分支FINSH_NODE_ID徙缴,判斷child是否為空试伙,若不為空,則調(diào)用
                    finsh_code_byte(FINSH_OP_LD_DWORD);
                    finsh_code_dword((long)node->id.syscall->func);

在代碼段中形成的結(jié)構(gòu)為:
0x24
函數(shù)地址
接著執(zhí)行FINSH_NODE_SYS_FUNC于样,調(diào)用:

                    finsh_code_byte(FINSH_OP_SYSCALL);
                    finsh_code_byte(parameters);

其中參數(shù)個數(shù)為0
在代碼段中存儲的數(shù)據(jù)為:
0x2C
0

  • 執(zhí)行虛擬機函數(shù)finsh_vm_run()疏叨,調(diào)用OP_ld_dword(),將函數(shù)地址入棧穿剖,finsh_sp->long_value=函數(shù)地址蚤蔓。然后調(diào)用OP_call(),獲取參數(shù)個數(shù)糊余,當(dāng)然為0秀又。后面就是相關(guān)的函數(shù)調(diào)用了。

好了贬芥,rt-thread的finsh shell就講到這里吐辙。實話說,其調(diào)用關(guān)系比較層疊蘸劈』杷眨看起來每個函數(shù)調(diào)用關(guān)系分開,寫得很好威沫。但是贤惯,目前很多的特性,支持力度還是有限的棒掠。

后想了想孵构,對于我們常見的說法,函數(shù)參數(shù)列表先入棧的是最后一個參數(shù)烟很,這里可以驗證一下:

找到函數(shù)proc_param_list浦译,這個就是處理參數(shù)列表的。
最終調(diào)用到proc_cast_expr() -->proc_type()---> 找到第一個參數(shù)(我們先分析參數(shù)全為數(shù)字),則token為finsh_token_type_value_int---->遞歸調(diào)用proc_cast_expr()--->proc_unary_expr()--->proc_postfix_expr()--->proc_primary_expr()--->finsh_node_new_int()溯职。
然后遞歸返回精盅,node->data_type = finsh_type_unknown;node->node_type = FINSH_NODE_VALUE_INT;
當(dāng)然在proc_postfix_expr()中調(diào)用的next_token,會設(shè)置token==finsh_token_type_comma谜酒。--->proc_cast_expr()--->next_token()--->FINSH_NODE_VALUE_INT叹俏。etc

    while (token == finsh_token_type_comma )
    {
        finsh_node_sibling(assign) = proc_assign_expr(self);

        if (finsh_node_sibling(assign) != NULL) assign = finsh_node_sibling(assign);
        else finsh_error_set(FINSH_ERROR_EXPECT_OPERATOR);

        next_token(token, &(self->token));
    }

從這個循環(huán)代碼中,可以發(fā)現(xiàn)是一直在建立siblings僻族。后面finsh_compile被調(diào)用粘驰,然后遞歸生效屡谐,最后一個參數(shù)被加入到棧中。

所以蝌数,很明顯咯愕掏,是遞歸導(dǎo)致的棧序改變。

還可以看看rt-thread的shell的函數(shù)調(diào)用顶伞,一層一層的饵撑,其實是按照操作符的優(yōu)先級來的。

2.1 一些擴展知識

在寫完一些內(nèi)容之后唆貌,發(fā)現(xiàn)finsh shell有一些命名技巧滑潘,這就涉及到一些知識。我也是邊看就搜咯锨咙。

2.1.1 siblings

這個在二叉樹上有介紹语卤,表示的是兄弟結(jié)點。二叉樹有完美二叉樹酪刀、完全二叉樹和完滿二叉樹粹舵。

寫到這里,我只好表示骂倘,我的樹知識欠缺齐婴,只好另外開一個欄,學(xué)習(xí)樹了稠茂。
鏈接:鏈接地址

2.2 分析開始

分析之初柠偶,這幾個結(jié)構(gòu)體比較重要:

3 picoC

源碼的下載地址為:下載鏈接

從其README.md上看,其說明了picoC是一個小的C解釋器腳本睬关。它最初是作為一個無人機的飛行系統(tǒng)板上的腳本語言诱担。核心的C源代碼是大約3500行。并不打算成為一個完整的ISO C實現(xiàn)电爹。picoc已經(jīng)在x86-32蔫仙,x86-64,PowerPC丐箩,arm摇邦,UltraSPARC,HP-PA和Blackfin處理器上測試過屎勘,并很容易地移植到新的目標(biāo)施籍。

picoc代碼上看,基本有如下幾塊:lex詞法解析概漱,table一個基本數(shù)據(jù)結(jié)構(gòu)(用于存放變量)丑慎,是個字符串hash表,heap管理內(nèi)存分配(包括了stack frame的實現(xiàn)), type做類型管理(基本型別和程序自定義的struct竿裂,typedef等),expression做表達(dá)式解析,variable變量管理分配釋放棧幀玉吁。

2.1 編譯

在NIX/Linux/POSIX主機上:

  • make
  • make test

之后,可以執(zhí)行./picoc腻异。
可以有三種執(zhí)行方式:

圖1

2.2 移植

  1. platform_XXX.c 包含一些支持性的函數(shù)进副。編譯器這樣才能在你的平臺上運行。例如如何向控制臺輸出字符悔常。
  2. platform_library.c包含一些函數(shù)庫影斑,這些庫是提供給用戶程序的。
  3. 有clibrary.c这嚣,包含用戶庫函數(shù)鸥昏,比如printf塞俱,這些是平臺無關(guān)的姐帚。

移植系統(tǒng),需要在platform.h中設(shè)置相應(yīng)的includes和defines障涯。在platform_XXX.c中編寫I/o函數(shù)罐旗。在platform_library.c 中編寫用戶函數(shù)。在picoc.c中編寫一些main函數(shù)內(nèi)容唯蝶。

platform.h默認(rèn)設(shè)置為unix主機(UNIX_HOST)九秀。

2.3 協(xié)議

picoc遵循"New BSD License".

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粘我,隨后出現(xiàn)的幾起案子鼓蜒,更是在濱河造成了極大的恐慌,老刑警劉巖征字,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件都弹,死亡現(xiàn)場離奇詭異,居然都是意外死亡匙姜,警方通過查閱死者的電腦和手機畅厢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氮昧,“玉大人框杜,你說我怎么就攤上這事⌒浞剩” “怎么了咪辱?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長椎组。 經(jīng)常有香客問我梧乘,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任选调,我火速辦了婚禮夹供,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仁堪。我一直安慰自己哮洽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布弦聂。 她就那樣靜靜地躺著鸟辅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莺葫。 梳的紋絲不亂的頭發(fā)上匪凉,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音捺檬,去河邊找鬼再层。 笑死,一個胖子當(dāng)著我的面吹牛堡纬,可吹牛的內(nèi)容都是我干的聂受。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼烤镐,長吁一口氣:“原來是場噩夢啊……” “哼蛋济!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起炮叶,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤碗旅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后镜悉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祟辟,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年积瞒,在試婚紗的時候發(fā)現(xiàn)自己被綠了川尖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡茫孔,死狀恐怖叮喳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缰贝,我是刑警寧澤馍悟,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站剩晴,受9級特大地震影響锣咒,放射性物質(zhì)發(fā)生泄漏侵状。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一毅整、第九天 我趴在偏房一處隱蔽的房頂上張望趣兄。 院中可真熱鬧,春花似錦悼嫉、人聲如沸艇潭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹋凝。三九已至,卻和暖如春总棵,著一層夾襖步出監(jiān)牢的瞬間鳍寂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工情龄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留迄汛,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓刃唤,卻偏偏與公主長得像隔心,于是被迫代替她去往敵國和親白群。 傳聞我的和親對象是個殘疾皇子尚胞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容