This page looks best with JavaScript enabled

c inline assambly and system call

 ·  ☕ 4 min read

簡介

這裡我們要學到如何利用c語言直接呼叫system call做到printf顯示的功能

範例

實做直接呼叫linux system code

根據1-1表格找到的資訊

關於此段指令為什麼填入1的理由

“mov $1, %%rdi\n”

1
2
3
4
#include <sys/syscall.h>
int main(void){
 syscall(SYS_write, 1, "hello, world!\n", 14);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void print_asm(char *arg1,long int size){ 
    __asm__ volatile(
        "mov $1, %%rax\n\t"//system call 編碼
        "mov $1, %%rdi\n\t"//fd 設定1 代表把字串輸入/dev/stdout 這裡就是螢幕輸出地方
        "mov %0,%%rsi\n\t"//輸入字串記憶體位置
        "mov %1, %%rdx\n\t" //這裡輸入字串長度 ,可以跟記憶體位置搭配來輸出到螢幕
        "syscall"//x64 要用此呼叫systemcall 不能在使用int $0x80
        :
        :"m" (arg1),"m" (size) //詳細請參考gcc inline asm
    );
}
int main(void) {
    char *d="hello, world!\n";
    print_asm(d,14);
return 0;
}

System call 傳遞約定

我們要先了解System call 傳遞約定才能知道要如何傳遞我們要的參數告訴linux作業系統

根據linux原始碼我們可以建立下面的表格

表格1-1

system call number
ABI規範1__x64_sys_writefdbufcount
x64 syscall%rax%rdi%rsi%rdx%r10%r8%r9

為什麼x64 syscall不符合abi?

%raxSystem call%rdi%rsi%rdx%r10%r8%r9
1__x64_sys_writefdbufcount

根據calling_conventions

C內嵌ASM

__asm__asm都可以使用
但只有__asm__ 可以在-ansi-std(標準c規範)下使用,原因

__asm__ asm-qualifiers ( AssemblerTemplate 
                      : OutputOperands
                      : InputOperands
                      : Clobbers
                      : GotoLabels)

asm-qualifiers

  • volatile

    volatile無法完美讓內嵌組合語言避免被編譯器更動
    Do not expect a sequence of asm statements to remain perfectly consecutive after compilation, even when you are using the volatile qualifier. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement.

AssemblerTemplate

這裡放組合語言

  • 指令換行"\n\t"

OutputOperands

[ [asmSymbolicName] ] constraint (cvariablename)

constraint

prefix = or +

這裡寫要輸出變數

InputOperands

這裡寫要輸入變數

Clobbers

編譯器不會用到Clobbers裏面設定的暫存器

When the compiler selects which registers to use to represent input and output operands, it does not use any of the clobbered registers. As a result, clobbered registers are available for any use in the assembler code.

這個範例會引發side effect,導致不符合預期
因為[arg1]"r" (arg1)r所以arg1數值會被塞到某個暫存器來使用,但接下來rax,rdi,rsi,rdx暫存器會被寫入,所以原本借放arg1數值的暫存器有可能被覆蓋導致不符合預期

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
long long int print_asm(char *arg1,long int size){ 
    long long int rax;
    __asm__ volatile(
        "mov $1, %%rax\t\n"//system call 編碼
        "mov $1, %%rdi\t\n"//fd 設定1 代表把字串輸入/dev/stdout 這裡就是螢幕輸出地方
        "mov %[arg1],%%rsi\t\n"//輸入字串記憶體位置
        "mov %[size], %%rdx\t\n" //這裡輸入字串長度 ,可以跟記憶體位置搭配來輸出到螢幕
        "syscall\t\n"//x64 要用此呼叫systemcall 不能在使用int $0x80
        "mov %%rax,%[rax]\t\n"
        :[rax]"=m" (rax)
        :[arg1]"r" (arg1),[size]"m" (size)//詳細請參考gcc inline asm
        : 
    );
    
    return rax;
}
int main(void) {
    char *d="hello, world!\n";
    int rax=print_asm(d,14);
    printf("%d",rax);
return 0;
}

告訴編譯器輸入與輸出不能用到"rax",“rdi”,“rsi”,“rdx"暫存器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
long long int print_asm(char *arg1,long int size){ 
    long long int rax;
    __asm__ volatile(
        "mov $1, %%rax\t\n"//system call 編碼
        "mov $1, %%rdi\t\n"//fd 設定1 代表把字串輸入/dev/stdout 這裡就是螢幕輸出地方
        "mov %[arg1],%%rsi\t\n"//輸入字串記憶體位置
        "mov %[size], %%rdx\t\n" //這裡輸入字串長度 ,可以跟記憶體位置搭配來輸出到螢幕
        "syscall\t\n"//x64 要用此呼叫systemcall 不能在使用int $0x80
        "mov %%rax,%[rax]\t\n"
        :[rax]"=r" (rax)
        :[arg1]"r" (arg1),[size]"m" (size)//詳細請參考gcc inline asm
        :"rax","rdi","rsi","rdx"
    );
    return rax;
}
int main(void) {
    char *d="hello, world!\n";
    int rax=print_asm(d,14);
    printf("%d",rax);
return 0;
}

解法二直接用記憶體位置,不利用暫存器借放

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
long long int print_asm(char *arg1,long int size){ 
    long long int rax;
    __asm__ volatile(
        "mov $1, %%rax\t\n"//system call 編碼
        "mov $1, %%rdi\t\n"//fd 設定1 代表把字串輸入/dev/stdout 這裡就是螢幕輸出地方
        "mov %[arg1],%%rsi\t\n"//輸入字串記憶體位置
        "mov %[size], %%rdx\t\n" //這裡輸入字串長度 ,可以跟記憶體位置搭配來輸出到螢幕
        "syscall\t\n"//x64 要用此呼叫systemcall 不能在使用int $0x80
        "mov %%rax,%[rax]\t\n"
        :[rax]"=r" (rax)
        :[arg1]"m" (arg1),[size]"m" (size)//詳細請參考gcc inline asm
        :
    );
    return rax;
}
int main(void) {
    char *d="hello, world!\n";
    int rax=print_asm(d,14);
    printf("%d",rax);
return 0;
}

GotoLabels

參考資源

C/C++ function definitions without assembly

How can I find the implementations of Linux kernel system calls?

linux system call
Inline Assembly & Memory Barrier

Share on

呂奕珣
WRITTEN BY
呂奕珣
SDN ML FINTECH HFT