--- tags: Linux2020 --- # kcalc-fixed & Linux Kernel 筆記 [kcalc-fixed](https://hackmd.io/@YLowy/Sy9fENauv) 本篇是在學習 kernel 時所做的筆記,把學習遇到整理出來 ## 為什麼會 Kernel Modules 有這個東西 [鳥哥私房菜](http://linux.vbird.org/linux_basic/0540kernel.php) --- ## Build Hello modules 首先,先來 Hello world ,先對整體運作可以有點概念 ![](https://i.imgur.com/qnSJ9CI.png) 本小節會介紹對於 Hello Module 的運作行解釋 ### kernel 版本 ``` $ uname -r 5.4.0-52-generic ``` ### 在 linux-headers 對應目錄下建立 Hello 建立 hello 資料夾並在其中建立 Makefile 以及 hello.c ``` /usr/src/linux-headers-5.4.0-52-generic/hello$ ls hello.c Makefile ``` 程式碼如下 ### hello.c ```c= #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_INFO "Hello, world\n"); return 0; } static void hello_exit(void) { printk(KERN_INFO "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit); ``` ### Makefile ``` obj-m := hello.o clean: rm -rf *.o *.ko *.mod.* *.symvers *.order *.mod.cmd *.mod ``` ### 對該 Makefile 檔案 make make 指令 : `make -C /usr/src/linux-headers-5.4.0-52-generic/ M='/usr/src/linux-headers-5.4.0-52-generic/hello/' modules` 指定檔案路徑使其產生執行檔 ``` $ sudo make -C /usr/src/linux-headers-5.4.0-52-generic/ M='/usr/src/linux-headers-5.4.0-52-generic/hello/' modules make: Entering directory '/usr/src/linux-headers-5.4.0-52-generic' CC [M] /usr/src/linux-headers-5.4.0-52-generic/hello//hello.o Building modules, stage 2. MODPOST 1 modules CC [M] /usr/src/linux-headers-5.4.0-52-generic/hello//hello.mod.o LD [M] /usr/src/linux-headers-5.4.0-52-generic/hello//hello.ko make: Leaving directory '/usr/src/linux-headers-5.4.0-52-generic' ``` 成功之後會產出許多檔案,這邊我們會用到的只有hello.ko ``` /usr/src/linux-headers-5.4.0-52-generic/hello$ ls -al total 112 drwxr-xr-x 2 root root 4096 十一 9 00:41 . drwxr-xr-x 8 root root 4096 十一 9 00:15 .. -rw-r--r-- 1 root root 324 十一 9 00:24 hello.c -rw-r--r-- 1 root root 3968 十一 9 00:41 hello.ko -rw-r--r-- 1 root root 334 十一 9 00:41 .hello.ko.cmd -rw-r--r-- 1 root root 56 十一 9 00:41 hello.mod -rw-r--r-- 1 root root 560 十一 9 00:41 hello.mod.c -rw-r--r-- 1 root root 195 十一 9 00:41 .hello.mod.cmd -rw-r--r-- 1 root root 2800 十一 9 00:41 hello.mod.o -rw-r--r-- 1 root root 31282 十一 9 00:41 .hello.mod.o.cmd -rw-r--r-- 1 root root 2056 十一 9 00:41 hello.o -rw-r--r-- 1 root root 31069 十一 9 00:41 .hello.o.cmd -rw-r--r-- 1 root root 84 十一 9 00:37 Makefile -rw-r--r-- 1 root root 56 十一 9 00:41 modules.order -rw-r--r-- 1 root root 0 十一 9 00:41 Module.symvers ``` ### insmod 產出 hello.ko 之後可以將其對 Kernel 進行掛載 ``` /usr/src/linux-headers-5.4.0-52-generic/hello$ sudo insmod hello.ko ``` `dmesg` 可以顯示 kernel 訊息 ``` [ 3824.676183] Hello, world ``` ### rmmod 其對 Kernel 進行卸載 ``` /usr/src/linux-headers-5.4.0-52-generic/hello$ sudo rmmod hello.ko ``` `dmesg` 顯示 kernel 訊息 ``` [ 3824.676183] Hello, world [ 3921.232076] Goodbye, cruel world ``` 以上為第一個 Kernel Module 掛載以及卸載 --- ## 對於 Kernel 程式碼解釋 ### printk kernel 對 log 進行輸出 `printk(KERN_INFO "Hello, world\n");` >#define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ ### __init [參考](https://huenlil.pixnet.net/blog/post/26476776)(這裡還沒用到) ``` /include/linux/init.h 43 #define __init __attribute__ ((__section__ (".init.text"))) __cold ``` ### 對其進入點 hello_init `static int hello_init(void)` ### 對其離開點 hello_exit `static void hello_exit(void);` --- # kcalc-fixed ## 對於 kcalc-fixed 的 module_init(calc_init) 對於該 cal 的載入點 `static int __init calc_init(void)` ### 1. register_chrdev `register_chrdev` 是用來在 kernel 中註冊 driver ,同時賦予這個 driver 相對的 maojr 與 minor number 由此定義可以將驅動程式將提供 open/read/write/release 4 個 system call 介面給 user application 先看 `struct file_operations` 部分 ```c= static struct file_operations fops = { .open = dev_open, .read = dev_read, .write = dev_write, .release = dev_release, }; ``` 註冊字元型驅動程式 ```c=153 major = register_chrdev(0, DEVICE_NAME, &fops); ``` > 第一個參數 : 為device file的major number > 第二個參數 : 驅動程式名稱 本處名稱 > #define DEVICE_NAME "calc" > 第三個參數 : 驅動程式的fops 之後 major 會被指派到一個回傳值 major number ### 2. class_create / device_create 自動建立裝置檔案結點,以下[參考資料](http://csw-dawn.blogspot.com/2012/01/linux-device-driver-8-character-device.html) ```c=161 char_class = class_create(THIS_MODULE, CLASS_NAME); ``` ```c=170 char_dev = 171 device_create(char_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); ``` 這裡要先了解 [udev](https://en.wikipedia.org/wiki/Udev), udev 是Linux kernel 2.6系列的裝置管理器。它主要的功能是管理/dev目錄底下的裝置節點 當新的模組載入時,udevd daemon 會進行偵測並且檢查 /sys 目錄,如果該驅動建立了 dev 檔案,檔案會存在 major, minor number ,因此 udevd 就可以為他建立裝置檔 因此若想讓 modules 支援 udev,必須先登入該 class 並且在/sys/class 目錄下建立 modules 程式資訊 `struct class *class_create(struct module *owner, const char *name);` > owner : 這裡放 THIS_MODULE > name : 這裡放 CLASS_NAME ,CLASS_NAME = "calc" 接著要建立 /sys/class/class 名稱/裝置名稱 這個檔案,用的是 `device_create()` ```c= struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) ``` > cls: 傳入由class_creat() 傳回的 class。 > parent: 是指定上層 class的時候使用的,傳入 NULL也行。 >devt: 是dev檔顯示的major/minor number,也可用 MKDEV巨集指定。 >drvdate: 是添加到裝置的資料,傳入 NULL也行。 >fmt: 裝置檔的名稱 這邊使用 DEVICE_NAME = "calc" 對於該 main.c 的 initial 行為大致回以上三步驟 --- ## 對於 kcalc-fixed 的 module_exit(calc_exit) 這邊會對上述 initial 時期所建立的資料進行清除 ```c=186 device_destroy(char_class, MKDEV(major, 0)); /* remove the device */ class_unregister(char_class); /* unregister the device class */ class_destroy(char_class); /* remove the device class */ unregister_chrdev(major, DEVICE_NAME); /* unregister the major number */ ``` 從最後創的開始釋放,分別對於 class 以及 device 移除以及取消註冊 上述 `module_init(calc_init)` `module_exit(calc_exit)` 分別為該程式進入起始點以及離開點 :::info 程式沒有要求一定要有 main() ,只要有對應起始位置 ::: --- ## prototype functions reference [The Linux Kernel ](https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html) 先定義該 4 個 prototype functions ```c=33 /* The prototype functions for the character driver */ static int dev_open(struct inode *, struct file *); static int dev_release(struct inode *, struct file *); static ssize_t dev_read(struct file *, char *, size_t, loff_t *); static ssize_t dev_write(struct file *, const char *, size_t, loff_t *); ``` ### dev_open ```c=48 static int dev_open(struct inode *inodep, struct file *filep) { msg_ptr = message; return 0; } ``` ### dev_release ```c=140 static int dev_release(struct inode *inodep, struct file *filep) { pr_info("Device successfully closed\n"); return 0; } ``` :::danger 感覺這裡是把一些 _init 時期以及 _exit 時期可以挪到這裡做 ? init 主要做登入及掛載,而在需要使用時候在針對其在 open 時期佔用資源,以達到有效運用資源的目標 ::: ### dev_read :::spoiler code ```c=54 static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) { int error_count = 0; if (*msg_ptr == 0) return 0; memset(message, 0, sizeof(char) * BUFF_SIZE); snprintf(message, 64, "%" PRIu64 "\n", (unsigned long long) result); size_of_message = strlen(message); error_count = copy_to_user(buffer, message, size_of_message); if (error_count == 0) { pr_info("size: %d result: %" PRIu64 "\n", size_of_message, result); while (len && *msg_ptr) { error_count = put_user(*(msg_ptr++), buffer++); len--; } if (error_count == 0) return (size_of_message); return -EFAULT; } else { pr_info("Failed to send %d characters to the user\n", error_count); return -EFAULT; } } ``` ::: ### dev_write reference [Here](https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html#read-and-write) write 的主體為 user space ,所以要以 User 寫進 Kernel 為想法 :::info **Write operation** Add the ability to write a message into kernel buffer to replace the predefined message. Implement the write function in the driver. Follow comments marked with TODO 5 Ignore the offset parameter at this time. You can assume that the driver buffer is large enough. You do not need to check the validity of the write function size argument. The prototype of a device driver’s operations is in the file_operations structure. Test using commands: ``` echo "arpeggio"> /dev/so2_cdev cat /dev/so2_cdev ``` 在本處也是透過此種方式對 calc module 寫入資料 ``` echo -ne "3*5\0" > /dev/calc ``` >**echo** >-n do not output the trailing newline -e enable interpretation of backslash escapes ::: `dev_write` 程式碼如下 : 第 `91` 行 : 首先對 message 做初始化,確保該 buffer 內皆為 0 第 `98` 行 : `copy_from_user(message, buffer, len);` >**copy_from_user** unsigned long copy_from_user(void *to, const void *from, unsigned long n); to : 目標地址(kernel) from : 源地址(user space) n : length return :成功返回 0,失敗返回沒有拷貝成功的資料位元組數 `message` 就可以得到 echo 所傳入字串 第 `101` 行 : 呼叫 calc() 對 `message` 進行處理 ```c=86 static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { memset(message, 0, sizeof(char) * BUFF_SIZE); if (len >= BUFF_SIZE) { pr_alert("Expression too long"); return 0; } copy_from_user(message, buffer, len); pr_info("Received %ld -> %s\n", len, message); calc(); return len; } ``` ![](https://i.imgur.com/1MMV8LO.png) --- ## kernel 所運作 dev_write() 程式碼 calc() 第 `128` 行 : 建立 var 並全部指派為 0 **Reference C99 Standard 6.7.8.21:** >If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration. 第 `129` 行 : 就是教材所寫的內容,會將 message 也就是 echo 所輸入值轉成表達式 e 第 `135` 行 : `result = expr_eval(e);` 對該表達式做計算 ```c=126 static void calc(void) { struct expr_var_list vars = {0}; struct expr *e = expr_create(message, strlen(message), &vars, user_funcs); if (!e) { pr_alert("Syntax error"); return; } result = expr_eval(e); pr_info("Result: %" PRIu64 "\n", result); expr_destroy(e, &vars); } ``` ---- ## expr express.h 宣告結構 ```c=13 struct expr { int type; union { struct { uint64_t value; } num; struct { uint64_t *value; } var; struct { vec_expr_t args; } op; struct { struct expr_func *f; vec_expr_t args; void *context; } func; } param; }; ``` param union 32 bytes expr structure 40 bytes (assignment issue while add an 4 bytes integer) **C99 standard §6.7.2.1/14 (§6.7.2.1/16 in C11 and C18)**: >The size of a union is sufficient to contain the largest of its members. The value of at most one of the members can be stored in a union object at any time. A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit-field, then to the unit in which it resides), and vice versa. **expr 為表達式之主要結構** ```graphviz digraph A{ rankdir=LR; A [shape=record fontsize=24 label="{struct expr (size = 40)}|{{0||1|2|3|4}|{{int type|assignment}|union param |...|...|...|...} }"] } ``` ```c=121 /* * Variables */ struct expr_var { uint64_t value; struct expr_var *next; char name[]; }; struct expr_var_list { struct expr_var *head; }; struct expr_var *expr_var(struct expr_var_list *vars, const char *s, size_t len); ``` ---- ## 透過 GDB 對 MathEx 做觀察 MathEx 在經過 make 之後會再 /build 資料夾中生成執行檔,透過 gdb 觀察在不同算式中程式 e 生成結構 ### 當 *s = "2+3" 時 >expr_type : OP_PLUS = 8 觀察 expr_eval 所作行為,會對於 type = OP_PLUS 結構中 回傳 buf[0] + buf[1],如果 buf[0]/[1] 為符號則會透過遞迴方式取得該值 ```c=220 case OP_PLUS: return expr_eval(&e->param.op.args.buf[0]) + expr_eval(&e->param.op.args.buf[1]); ``` 觀察 gdb 部分,由於 `type = 8` ,所以 `param ` 只需要觀察 `buf` ``` (gdb) p *e $1 = {type = 8, param = {num = {value = 1.68972234e+13}, var = {value = 0x55555575e310}, op = { args = {buf = 0x55555575e310, len = 2, cap = 2}}, func = {f = 0x55555575e310, args = { buf = 0x200000002, len = 0, cap = 0}, context = 0x0}}} ``` ```graphviz digraph A{ rankdir=LR; A [shape=record fontsize=24 label="{{0||1|2|3|4}|{{ type = 8 |assignment}|union param |buf = xxxx5e310|{{len =2}|{cap = 2}}|...|...} }"] e->A } ``` 觀察 `buf[0]` 以及 `buf[1]` ``` (gdb) p &(e->param ->op ->args ->buf[1]) $11 = (struct expr *) 0x55555575e338 (gdb) p &(e->param ->op ->args ->buf[0]) $12 = (struct expr *) 0x55555575e310 ``` ``` (gdb) p e->param ->op ->args ->buf[0] $13 = {type = 25, param = {num = {value = 2}, var = {value = 0x40000000}, op = {args = { buf = 0x40000000, len = 0, cap = 0}}, func = {f = 0x40000000, args = {buf = 0x0, len = 0, cap = 0}, context = 0x0}}} (gdb) p e->param ->op ->args ->buf[1] $14 = {type = 25, param = {num = {value = 3}, var = {value = 0x40400000}, op = {args = { buf = 0x40400000, len = 0, cap = 0}}, func = {f = 0x40400000, args = {buf = 0x0, len = 0, cap = 0}, context = 0x0}}} ``` >expr_type : OP_CONST = 25 在 expr_eval 中對於 OP_CONST 之處理 : ```c=288 case OP_CONST: return e->param.num.value; ``` ```graphviz digraph A{ buf [shape=record fontsize=24 label="{{<S>buf[0]}|{<SS>buf[1]}}"] A [shape=record fontsize=24 label="{{<S>0||1|2|3|4}|{{ type = 8 |assignment}|union param |<buf>buf = xxxx5e310|{{len =2}|{cap = 2}}|...|...} }"] e->A:S rankdir=LR; B [shape=record fontsize=24 label="{{<S>0||1|2|3|4}|{{ type = 25 |assignment}|union param |{{value = 2}|{...}}|{...}|...|...} }"] C [shape=record fontsize=24 label="{{<S>0||1|2|3|4}|{{ type = 25 |assignment}|union param |{{value = 3}|{...}}|{...}|...|...} }"] xxxx5e310->B:S xxxx5e338->C:S A:buf -> buf:S buf:S ->xxxx5e310 buf:SS ->xxxx5e338 } ``` 以上為對於 "2+3" 所完成的表示式結構,並透過 exal 計算可能正確達案 "5" ### 當 *s = "add(2,3)" 時 ```c=95 struct expr_func *expr_func(struct expr_func *funcs, const char *s, size_t len); ``` ```c=85 /* * Functions */ struct expr_func { const char *name; exprfn_t f; exprfn_cleanup_t cleanup; size_t ctxsz; }; ``` ``` typedef float (*exprfn_t)(struct expr_func *f, vec_expr_t args, void *context); ``` ``` (gdb) p e->param ->func ->f-> cleanup ctxsz f name (gdb) p e->param ->func ->f-> cleanup ctxsz f name (gdb) p e->param ->func ->f->name $16 = 0x55555555b466 "add" (gdb) p e->param ->func ->f->f $17 = (exprfn_t) 0x555555554d80 <add> (gdb) p e->param ->func ->f->ctxsz $18 = 0 (gdb) p e->param ->func ->f->cleanup $19 = (exprfn_cleanup_t) 0x0 ``` ``` (gdb) p e->param ->func ->args ->buf[0] $5 = {type = 25, param = {num = {value = 2}, var = {value = 0x40000000}, op = {args = { buf = 0x40000000, len = 0, cap = 0}}, func = {f = 0x40000000, args = {buf = 0x0, len = 0, cap = 0}, context = 0x0}}} (gdb) p e->param ->func ->args ->buf[1] $6 = {type = 25, param = {num = {value = 3}, var = {value = 0x40400000}, op = {args = { buf = 0x40400000, len = 0, cap = 0}}, func = {f = 0x40400000, args = {buf = 0x0, len = 0, cap = 0}, context = 0x0}}} ``` ``` (gdb) p e->param ->func ->f $28 = (struct expr_func *) 0x55555575d020 <user_funcs> (gdb) p e->param ->func ->f ->f $29 = (exprfn_t) 0x555555554d80 <add> ``` 此處指向的 add 即是我們在 function pointer array 中所存放並指向的函式位置 ```c=5 /* Custom function that returns the sum of its two arguments */ static float add(struct expr_func *f, vec_expr_t args, void *c) { float a = expr_eval(&vec_nth(&args, 0)); float b = expr_eval(&vec_nth(&args, 1)); return a + b; } ``` exprfn_t 為一 function pointer ```c=56 typedef float (*exprfn_t)(struct expr_func *f, vec_expr_t args, void *context); ``` 藉由上述可以得到對於函式表達式結構 在 expr_func 函式中會對於該名稱的函式在類似 Array 的架構中找尋,找尋到對應的該函式位址 ```c=148 /* * Functions */ struct expr_func *expr_func( struct expr_func *funcs, const char *s, size_t len) { for (struct expr_func *f = funcs; f->name; f++) { if (strlen(f->name) == len && strncmp(f->name, s, len) == 0) { return f; } } return NULL; } ``` ```graphviz digraph A{ E [shape=record fontsize=24 label="{{<S>0||1|2|3|4}|{{ type = 27 |assignment}|union param (func) |<f>f =0x55555575d020(exprfn_t)|<buf> (arg) buf = 0x55555575e2e0|{(arg)len = 2|(arg)cap = 2}| context = 0x0} }"] buf [shape=record fontsize=24 label="{{<S>buf[0]}|{<SS>buf[1]}}"] A [shape=record fontsize=24 label="{{...|<add> add function|XXX function|...}}"] e->E:S E:f ->A:add rankdir=LR; B [shape=record fontsize=24 label="{{<S>0||1|2|3|4}|{{ type = 25 |assignment}|union param |{{value = 2}|{...}}|{...}|...|...} }"] C [shape=record fontsize=24 label="{{<S>0||1|2|3|4}|{{ type = 25 |assignment}|union param |{{value = 3}|{...}}|{...}|...|...} }"] xxxx5e2e0->B:S xxxx5e308->C:S E:buf -> buf:S buf:S ->xxxx5e2e0 buf:SS ->xxxx5e308 } ``` 在 expr_eval 時期會對於該 function 以及 argument 值進行回傳,已得到我們所需要答案 ```c=292 case OP_FUNC: return e->param.func.f->f( e->param.func.f, e->param.func.args, e->param.func.context); ``` ### 當 *s = "x = 40, add(2, x)" 時 這也是給定 MathEx 程式中的 simple.c 原本的輸入值 在 type = 24(OP_COMMA) 時, eval 會對前述 ( buf[0] ) 找尋變數值,並且回傳(buf[1]) 的計算值 ```c=285 case OP_COMMA: expr_eval(&e->param.op.args.buf[0]); return expr_eval(&e->param.op.args.buf[1]); ``` 透過 gdb 可以得到對於該函式的前述與計算值,而 buf [1] 也會指向 type = OP_COMMA 的 expr ``` (gdb) p e->param ->op ->args ->buf [0] $45 = {type = 23, param = {num = {value = 1.6897257e+13}, var = {value = 0x55555575e330}, op = { args = {buf = 0x55555575e330, len = 2, cap = 2}}, func = {f = 0x55555575e330, args = { buf = 0x200000002, len = 0, cap = 0}, context = 0x0}}} (gdb) p e->param ->op ->args ->buf [1] $46 = {type = 27, param = {num = {value = 1.68921399e+13}, var = { value = 0x55555575d020 <user_funcs>}, op = {args = {buf = 0x55555575d020 <user_funcs>, len = 1433789408, cap = 21845}}, func = {f = 0x55555575d020 <user_funcs>, args = { buf = 0x55555575e3e0, len = 2, cap = 2}, context = 0x0}}} ``` ```graphviz digraph A{ rankdir=LR; A [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 24|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] buf1 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] A:buf->buf1:0 B [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 23|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] C [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 27|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] buf1:0->B:S buf1:1->C:S } ``` 首先先觀察 buf[0] 對於變數之定義 : 在 type = 23(OP_ASSIGN) 時行為 ```c=24 #define vec_nth(v, i) (v)->buf[i] ``` ```c=279 case OP_ASSIGN: n = expr_eval(&e->param.op.args.buf[1]); if (vec_nth(&e->param.op.args, 0).type == OP_VAR) { *e->param.op.args.buf[0].param.var.value = n; } return n; ``` 在 gdb 觀察此結構 ``` (gdb) p e->param ->op ->args ->buf[0]->param ->op ->args ->buf [0] $61 = {type = 26, param = {num = {value = 1.68970389e+13}, var = {value = 0x55555575e260}, op = { args = {buf = 0x55555575e260, len = 0, cap = 0}}, func = {f = 0x55555575e260, args = { buf = 0x0, len = 0, cap = 0}, context = 0x0}}} (gdb) p e->param ->op ->args ->buf[0]->param ->op ->args ->buf [1] $62 = {type = 25, param = {num = {value = 40}, var = {value = 0x42200000}, op = {args = { buf = 0x42200000, len = 0, cap = 0}}, func = {f = 0x42200000, args = {buf = 0x0, len = 0, cap = 0}, context = 0x0}}} ``` 對應到 eval 時期所回傳函式 (OP_CONST = 25,OP_VAR = 26) ```c=288 case OP_CONST: return e->param.num.value; case OP_VAR: return *e->param.var.value; ``` `OP_VAR` 會回傳 var.value,該回傳值為 float pointer type ,該指標指向 ``` (gdb) p e->param ->op ->args ->buf[0]->param ->op ->args ->buf [0]->param ->var ->value [0] $66 = 40 ``` ```graphviz digraph A{ rankdir=LR; A [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 24|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] buf1 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] A:buf->buf1:0 B [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 23|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] C [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 27|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] buf1:0->B:S buf1:1->C:S buf2 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] D [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 25|assignment}|union param (func) |{{num.value = 40}|XXXXXXXXX} |0| 0 | 0 } }"] E [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 26|assignment}|union param (func) |<varptr>var.value |0| 0 | 0 } }"] f40 [shape = record fontsize=27 label = "40"] B:buf ->buf2:0 buf2:0->E:S buf2:1->D:S E:varptr->f40 } ``` 再來觀察 buf[1] 的部分 ``` (gdb) p e->param ->op ->args ->buf[1] $78 = {type = 27, param = {num = {value = 1.68921399e+13}, var = { value = 0x55555575d020 <user_funcs>}, op = {args = {buf = 0x55555575d020 <user_funcs>, len = 1433789408, cap = 21845}}, func = {f = 0x55555575d020 <user_funcs>, args = { buf = 0x55555575e3e0, len = 2, cap = 2}, context = 0x0}}} ``` ```graphviz digraph A{ rankdir=LR; A [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 24|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] buf1 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] A:buf->buf1:0 B [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 23|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] C [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 27|assignment}|union param (func) |<fun>f = 0x55555575d020 |<buf> buf = 0x55555575e3e0|{{len = 2}|{cap = 2}}|context = 0x0 }}"] buf1:0->B:S buf1:1->C:S buf2 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] D [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 25|assignment}|union param (func) |{{num.value = 40}|XXXXXXXXX} |0| 0 | 0 } }"] E [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 26|assignment}|union param (func) |<varptr>var.value |0| 0 | 0 } }"] f40 [shape = record fontsize=27 label = "40"] func [shape=record fontsize=24 label="{{...|<add> add function|XXX function|...}}"] B:buf ->buf2:0 C:fun->func:add buf2:0->E:S buf2:1->D:S E:varptr->f40 } ``` 這裡與上述函式 add 操作相同 ``` (gdb) p e->param ->op ->args ->buf[1]->param ->func ->args ->buf [0] $80 = {type = 25, param = {num = {value = 2}, var = {value = 0x40000000}, op = {args = { buf = 0x40000000, len = 0, cap = 0}}, func = {f = 0x40000000, args = {buf = 0x0, len = 0, cap = 0}, context = 0x0}}} (gdb) p e->param ->op ->args ->buf[1]->param ->func ->args ->buf [1] $81 = {type = 26, param = {num = {value = 1.68970389e+13}, var = {value = 0x55555575e260}, op = { args = {buf = 0x55555575e260, len = 0, cap = 0}}, func = {f = 0x55555575e260, args = { buf = 0x0, len = 0, cap = 0}, context = 0x0}}} ``` 在取出 x 參數值對應 value 的位址取原本被記錄 x 的值 ``` (gdb) p e->param ->op ->args ->buf[1]->param ->func ->args ->buf [1]->param ->var ->value [0] $86 = 40 ``` ```graphviz digraph A{ rankdir=LR; A [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 24|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] buf1 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] A:buf->buf1:0 B [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 23|assignment}|union param (func) |<buf>buf =0x55555575e440 |0| 0 | 0 } }"] C [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 27|assignment}|union param (func) |<fun>f = 0x55555575d020 |<buf> buf = 0x55555575e3e0|{{len = 2}|{cap = 2}}|context = 0x0 }}"] buf1:0->B:S buf1:1->C:S buf2 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] D [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 25|assignment}|union param (func) |{{num.value = 40}|XXXXXXXXX} |0| 0 | 0 } }"] E [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 26|assignment}|union param (func) |<varptr>var.value |0| 0 | 0 } }"] f40 [shape = record fontsize=27 label = "40"] buf3 [shape=record fontsize=27 label="{<0>buf[0]}|{<1>buf[1]}"] G [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 25|assignment}|union param (func) |{{num.value = 2}|XXXXXXXXX} |0| 0 | 0 } }"] H [shape=record fontsize=27 label="{{<S>0||1|2|3|4}|{{ type = 26|assignment}|union param (func) |<varptr>var.value |0| 0 | 0 } }"] func [shape=record fontsize=24 label="{{...|<add> add function|XXX function|...}}"] B:buf ->buf2:0 C:buf ->buf3:0 buf3:0 ->G:S buf3:1->H:S C:fun->func:add buf2:0->E:S buf2:1->D:S E:varptr->f40 H:varptr->f40 } ``` 以上為透過 MathEx 對於基本的操作的 expr 結構,也會運用以上概念操作在 kacl-fixed 中 ## expr_create 將輸入的字串數學表達式編譯成 `struct expr` 型態,並且回傳 --- ## expr_eval 對於取得的表示式中,從第一個值開始判斷每一個 e 的 type 以決定套用在何種計算函式,這裡有用到遞迴的方式大幅簡化其計算的行為 另外這裡透過 [Reverse Polish Notation](https://hackmd.io/@sysprog/2020-kcalc#-%E6%95%B8%E5%AD%B8%E5%BC%8F%E8%A8%88%E7%AE%97%E5%99%A8)計算,以遞迴方式就可以不使用原先教材所說的 Stack 方式計算 ```c=348 uint64_t expr_eval(struct expr *e) { uint64_t n; switch (e->type) { case OP_UNARY_MINUS: /* OK */ return minus(0, expr_eval(&e->param.op.args.buf[0])); ... case OP_MINUS: /* OK */ return minus(expr_eval(&e->param.op.args.buf[0]), expr_eval(&e->param.op.args.buf[1])); ``` 像是普通的符號運算就做對應之操作 在 expression.c 中有先對其每一個 type 行宣告 ```c=42 enum expr_type { OP_UNKNOWN, OP_UNARY_MINUS, // 對該值取負數 0 - &e->param.op.args.buf[0]) OP_UNARY_LOGICAL_NOT, // 對該值取 NOT OP_UNARY_BITWISE_NOT, // Bitwise 操作 NOT OP_POWER, OP_DIVIDE, OP_MULTIPLY, OP_REMAINDER, OP_PLUS, OP_MINUS, OP_SHL, OP_SHR, OP_LT, OP_LE, OP_GT, OP_GE, OP_EQ, OP_NE, OP_BITWISE_AND, OP_BITWISE_OR, OP_BITWISE_XOR, OP_LOGICAL_AND, OP_LOGICAL_OR, OP_ASSIGN, OP_COMMA, OP_CONST, OP_VAR, OP_FUNC, }; ```