# linux2021: dmefs
###### tags: `linux2021`
:::warning
你的 GitHub 帳號名稱真棒,好似生來就是要開發 Linux 核心原始程式碼,後者有一堆 fs (file system)
:notes: jserv
:::
:::success
感謝老師的注意!
這名稱是 **D**ennis + **me**mory + **f**ile **s**ystem 而得,確實是為了 Linux 而創
:tada: dmefs
:::
## 測驗 α - 1
舉出 Linux 核心原始程式碼裡頭 bit rotation 的案例並說明
在 `include/linux/bitops.h`,Linux Kernel 定義了一連串 inline bit rotation function
以 64 bit rotation 為例
rotation left 為 `static inline __u64 rol64(__u64 word, unsigned int shift)`
rotation right 為 `static inline __u64 ror64(__u64 word, unsigned int shift)`
在 Linux Kernel 版本`v5.14-rc1`中,搜尋`ror32`發現在`lib/crypto/sha256.c`[1]有使用,程式碼如下:
```cpp
#define e0(x) (ror32(x, 2) ^ ror32(x, 13) ^ ror32(x, 22))
#define e1(x) (ror32(x, 6) ^ ror32(x, 11) ^ ror32(x, 25))
#define s0(x) (ror32(x, 7) ^ ror32(x, 18) ^ (x >> 3))
#define s1(x) (ror32(x, 17) ^ ror32(x, 19) ^ (x >> 10))
```
SHA256[2] 是 SHA-2 (Secure Hash Algorithm - 2)的其中一種演算法,這種演算法會使用到 ror32
而搜尋`rol32`則發現在`fs/ext4/hash.c`[3]有使用,摘錄程式碼如下:
```cpp
/*
* The generic round function. The application is so specific that
* we don't bother protecting all the arguments with parens, as is generally
* good macro practice, in favor of extra legibility.
* Rotation is separate from addition to prevent recomputation
*/
#define ROUND(f, a, b, c, d, x, s) \
(a += f(b, c, d) + x, a = rol32(a, s))
```
MD4 的計算過程中會使用到
在搜尋的過程中發現 hash algorithm 常使用到 bit rotation
reference:
[1]https://en.wikipedia.org/wiki/SHA-2
[2]https://elixir.bootlin.com/linux/v5.14-rc1/source/lib/crypto/sha256.c#L50
[3]https://elixir.bootlin.com/linux/v5.14-rc1/source/fs/ext4/hash.c#L45
## 測驗 α - 2
題目: x86_64 指令集具備 rotr 和 rotl 指令,上述 C 程式碼經過編譯器最佳化 (例如使用 gcc) 後,能否運用到這二個指令呢?
首先觀察`rotl8` 及`rotr8`,測試程式碼如下,名稱為`rot8.c`
```clike=
#include <stdint.h>
#include <stdio.h>
#define __DECLARE_ROTATE(bits, type) \
static inline type rotl##bits(const type v, int c) \
{ \
const int mask = (bits) - (1); \
c &= mask; \
\
return (v << c) | (v >> (-c & mask)); \
} \
\
static inline type rotr##bits(const type v, int c) \
{ \
const int mask = (bits) - (1); \
c &= mask; \
\
return (v >> c) | (v << (-c & mask)); \
}
#define DECLARE_ROTATE(bits) __DECLARE_ROTATE(bits, uint##bits##_t)
DECLARE_ROTATE(8);
void
foo(int i)
{
printf("rotl8: 0x%x\n", rotl8(i, 1));
printf("rotr8: 0x%x\n", rotr8(i, 1));
}
int
main(int argc, char const* argv[])
{
uint8_t i = 0x88;
foo(i);
return 0;
}
```
執行以下指令後可以產生無最佳化 assembly code ,名稱為`rot8.s`
`$ gcc -S -fverbose-asm -O0 rot8.c`
摘錄`rot8.s`中關於`rotl8`的操作
```=
.text
.type rotl8, @function
rotl8:
.LFB0:
.cfi_startproc
pushq %rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #,
.cfi_def_cfa_register 6
movl %edi, %eax # v, tmp93
movl %esi, -24(%rbp) # c, c
movb %al, -20(%rbp) # tmp94, v
# rotate.c:23: DECLARE_ROTATE(8);
movl $7, -4(%rbp) #, mask
movl -4(%rbp), %eax # mask, tmp95
andl %eax, -24(%rbp) # tmp95, c
movzbl -20(%rbp), %edx # v, _1
movl -24(%rbp), %eax # c, tmp96
movl %eax, %ecx # tmp96, tmp100
sall %cl, %edx # tmp100, _1
movl %edx, %eax # _1, _2
movl %eax, %esi # _2, _3
movzbl -20(%rbp), %edx # v, _4
movl -24(%rbp), %eax # c, tmp97
negl %eax # _5
andl -4(%rbp), %eax # mask, _6
movl %eax, %ecx # _6, tmp102
sarl %cl, %edx # tmp102, _4
movl %edx, %eax # _4, _7
orl %esi, %eax # _3, _9
popq %rbp #
.cfi_def_cfa 7, 8
ret
.cfi_endproc
```
從 line 16 開始是 rotl8 的 function body,操作就跟`rot8.c`的定義一樣,並沒有使用`rotl`的指令。我把 c 的程式碼當成註解改寫一下`rot8.s`,如下
```=
.text
.type rotl8, @function
rotl8:
.LFB0:
.cfi_startproc
pushq %rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #,
.cfi_def_cfa_register 6
movl %edi, %eax # v, tmp93
movl %esi, -24(%rbp) # c, c
movb %al, -20(%rbp) # tmp94, v
# rotate.c:23: DECLARE_ROTATE(8);
# const int mask = (bits) - (1)
movl $7, -4(%rbp) #, mask
movl -4(%rbp), %eax # mask, tmp95
# c &= mask;
andl %eax, -24(%rbp) # tmp95, c
movzbl -20(%rbp), %edx # v, _1
movl -24(%rbp), %eax # c, tmp96
movl %eax, %ecx # tmp96, tmp100
# v << c
sall %cl, %edx # tmp100, _1
movl %edx, %eax # _1, _2
movl %eax, %esi # _2, _3
movzbl -20(%rbp), %edx # v, _4
movl -24(%rbp), %eax # c, tmp97
# -c
negl %eax # _5
# -c & mask
andl -4(%rbp), %eax # mask, _6
movl %eax, %ecx # _6, tmp102
# v >> (-c & mask)
sarl %cl, %edx # tmp102, _4
movl %edx, %eax # _4, _7
# (v << c) || (v >> (-c & mask))
orl %esi, %eax # _3, _9
popq %rbp #
.cfi_def_cfa 7, 8
ret
.cfi_endproc
```
接著執行以下指令產生 O1 後的程式碼
`$ gcc -S -fverbose-asm -O1 rot8.c`
發現`rotl8`及`rotr8`的 function body 已經不見了,觀察 foo function body,可以看見編譯器使用`rolb`及`rorb`最佳化
```=
.text
.globl foo
.type foo, @function
foo:
.LFB25:
.cfi_startproc
endbr64
pushq %rbx #
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movl %edi, %ebx # tmp92, i
# rotate.c:31: printf("rotl8: 0x%x\n", rotl8(i, 1));
movl %edi, %edx # i, tmp88
rolb %dl # tmp88
movzbl %dl, %edx # tmp88, tmp89
# /usr/include/x86_64-linux-gnu/bits/stdio2.h:107: return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
leaq .LC0(%rip), %rsi #,
movl $1, %edi #,
movl $0, %eax #,
call __printf_chk@PLT #
# rotate.c:32: printf("rotr8: 0x%x\n", rotr8(i, 1));
rorb %bl # tmp90
movzbl %bl, %edx # tmp90, tmp91
# /usr/include/x86_64-linux-gnu/bits/stdio2.h:107: return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
leaq .LC1(%rip), %rsi #,
movl $1, %edi #,
movl $0, %eax #,
call __printf_chk@PLT #
# rotate.c:39: }
popq %rbx #
.cfi_def_cfa_offset 8
ret
.cfi_endproc
```
總結上述觀察,沒有最佳化時,不會使用`rol`, `ror`指令,開啟最佳化`O1`時,就會使用`rol`, `ror`指令
reference:
https://en.wikipedia.org/wiki/X86_instruction_listings
https://stackoverflow.com/questions/8021874/how-can-i-compile-to-assembly-with-gcc
https://stackoverflow.com/questions/38335212/calling-printf-in-x86-64-using-gnu-assembler
## 測驗 β - 1
說明 align_up 程式碼的運作原理
已知align_up 可針對給定的 alignment 數值,輸出大於等於 alignment 的記憶體對齊地址
```c=
#include <stdint.h>
static inline uintptr_t align_up(uintptr_t sz, size_t alignment)
{
uintptr_t mask = alignment - 1;
if ((alignment & mask) == 0) { /* power of two? */
return ((sz + mask) & ~mask);
}
return (((sz + mask) / alignment) * alignment);
}
```
首先看一般情況,也就是 alignment 不是 power of 2 時,
對齊後的記憶體位置為:
$result = ((sz + (alignment - 1)) / alginment) * alignment$
分為兩種情況討論
|sz MOD alignment|result|
|----------------|------|
| 0 | sz |
| not 0 | sz+1 |
當 sz 可被 alignment 整除時,加上 (alignment-1)之後除以 alignement 不會進位,但只要 sz 除以 aliment 不為0,加上 (alignment-1)之後除以 alignement 一定會進位,所以:
((**(sz + mask)** / alignment) * alignment) 是為了進位
(((sz + mask) **/ alignment) * alignment**) 是為了將 mask 的值清零
所有情況都可以這樣計算,不過當 alignment 是 power of 2 時可以只用 bit operation 算出結果,不需要**乘除**這種耗費時間的運算
當 alignment 是 power of 2 時,想法一樣,先進位,再把 mask 清零,所以:
**(sz + mask)** & ~mask 進位
(sz + mask) **& ~mask** 把 mask 的值清零
## 測驗 β - 2
在 Linux 核心原始程式碼找出類似 align_up 的程式碼,並舉例說明其用法
在`include/uapi/linux/const.h`可以發現
```c=
#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))
```
`include/linux/align.h`有使用到 __ALIGN_KERNEL,分別是`ALIGN`及`ALIGN_DOWN`
```c=
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_ALIGN_H
#define _LINUX_ALIGN_H
#include <linux/const.h>
/* @a is a power of 2 value */
#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
#define ALIGN_DOWN(x, a) __ALIGN_KERNEL((x) - ((a) - 1), (a))
#define __ALIGN_MASK(x, mask) __ALIGN_KERNEL_MASK((x), (mask))
#define PTR_ALIGN(p, a) ((typeof(p))ALIGN((unsigned long)(p), (a)))
#define PTR_ALIGN_DOWN(p, a) ((typeof(p))ALIGN_DOWN((unsigned long)(p), (a)))
#define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0)
#endif /* _LINUX_ALIGN_H */
```
`ALIGN` 的功能就如同 align_up ,針對給定的 alignment 數值,輸出大於等於 alignment 的記憶體對齊地址:
`ALIGN_DOWN` 則是針對給定的 alignment 數值,輸出小於等於 alignment 的記憶體對齊地址
參考執行輸出
```c
ALIGN_DOWN(120, 4) = 120
ALIGN_DOWN(121, 4) = 120
ALIGN_DOWN(122, 4) = 120
ALIGN_DOWN(123, 4) = 120
```
`ALIGN` 在 `arch/riscv/include/asm/processor.h` 有被使用,用來計算在 riscv 架構 kernel stack 中,registers 儲存的位置
```c=
// arch/riscv/include/asm/processor.h
#define STACK_ALIGN 16
#define task_pt_regs(tsk) \
((struct pt_regs *)(task_stack_page(tsk) + THREAD_SIZE \
- ALIGN(sizeof(struct pt_regs), STACK_ALIGN)))
#define KSTK_EIP(tsk) (task_pt_regs(tsk)->epc)
#define KSTK_ESP(tsk) (task_pt_regs(tsk)->sp)
```
THREAD_SIZE 在 64位元為 16KB
`KSTK_EIP`是 Privileged Control Registers - Exception Program Counter
`KSTK_ESP`是 General-purpose Registers - Stack pointer
`ALIGN_DOWN` 在 `arch/arm64/kernel/module-plts.c`有被使用,用來判斷兩個 structure 是否相同,程式碼如下:
```c=
bool plt_entries_equal(const struct plt_entry *a, const struct plt_entry *b)
{
u64 p, q;
/*
* Check whether both entries refer to the same target:
* do the cheapest checks first.
* If the 'add' or 'br' opcodes are different, then the target
* cannot be the same.
*/
if (a->add != b->add || a->br != b->br)
return false;
p = ALIGN_DOWN((u64)a, SZ_4K);
q = ALIGN_DOWN((u64)b, SZ_4K);
/*
* If the 'adrp' opcodes are the same then we just need to check
* that they refer to the same 4k region.
*/
if (a->adrp == b->adrp && p == q)
return true;
return (p + aarch64_insn_adrp_get_offset(le32_to_cpu(a->adrp))) ==
(q + aarch64_insn_adrp_get_offset(le32_to_cpu(b->adrp)));
}
```
reference:
https://www.ocf.berkeley.edu/~qmn/linux/riscv.html