点击上方蓝字 江湖评谈设为关注/星标
前言
前一篇:,里面了解了下Go可执行文件,Main入口并没有经过Glibc,而是有Go自己的库调用的。本篇继续扩展,看下Rust。先说下结论,实际上经过个人测试,除了Go,Rust/.NET/C/C++都是经过Glibc来调用Main的。
问题
首先解决下上一篇从.NET9看Golang的一个问题,也即是Go的main调用地方实际上是/usr/lib/go-1.18/src/runtime/proc.go:259堆栈处,实际249处,代码如下:
func main() {
146 g := getg()
147
148 // Racectx of m0->g0 is used only as the parent of the main goroutine.
149 // It must not be used for anything else.
150 g.m.g0.racectx = 0
151
152 // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
153 // Using decimal instead of binary GB and MB because
154 // they look nicer in the stack overflow failure message.
155 if goarch.PtrSize == 8 {
156 maxstacksize = 1000000000
157 } else {
158 maxstacksize = 250000000
159 }
//中间省略
249 fn := main_main // make an indirect call, as the linker doesn't know the address o f the main package when laying down the runtime
250 fn()
251 if raceenabled {
252 racefini()
253 }
}
可以看到Go实际是自举自己调用了main,然后通过这个main调用了用户写的那个main.
249 fn := main_main // make an indirect call, as the linker doesn't know the address o f the main package when laying down the runtime
250 fn()
而在这之前实际上它还调用了一个syscall进行一些用户main调用之前的操作。
306 MOVL size+24(FP), R10
307 MOVL $SYS_rt_sigprocmask, AX
308 SYSCALL
309 CMPQ AX, $0xfffffffffffff001
310 JLS 2(PC)
311 MOVL $0xf1, 0xf1 // crash
312 RET
那么用户态下的linux第一个启动Go的是谁呢?
1568 // The top-most function running on a goroutine
1569 // returns to goexit+PCQuantum.
1570 TEXT runtime·goexit(SB),NOSPLIT|TOPFRAME,$0-0
1571 BYTE $0x90 // NOP
1572 CALL runtime·goexit1(SB) // does not return
1573 // traceback from goexit1 must hit code range of goexit
1574 BYTE $0x90 // NOP
在goroutine上运行的最顶级函数,返回goexit+PCQuantum。实际上Linux用户态的main就是有它(runtime·goexit(SB))调用的,后者则调用go的用户态main,运行整个go程序。
Rust
#curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 安装
#cargo new rust-hello
#cd rust-hello
#cargo build
#cd target/debug
#./hello-rust
注意如果单个.rs文件Compile,是不被lldb识别的没有sysmbol,所以这里需要通过cargo创建项目。也可以rust-lldb,但需要脚本支撑,这里不做讨论。
#lldb hello-rust
(lldb)b main
(lldb)r
(lldb)bt
* thread #1, name = 'hello-rust', stop reason = breakpoint 1.2
* frame #0: 0x000055555555b7e0 hello-rust`main
frame #1: 0x00007ffff7c29d90 libc.so.6`__libc_start_call_main(main=(hello-rust`main), argc=1, argv=0x00007fffffffdec8) at libc_start_call_main.h:58:16
frame #2: 0x00007ffff7c29e40 libc.so.6`__libc_start_main_impl(main=(hello-rust`main), argc=1, argv=0x00007fffffffdec8, init=0x00007ffff7ffd040, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=0x00007fffffffdeb8) at libc-start.c:392:3
frame #3: 0x000055555555b635 hello-rust`_start + 37
看到了亲切的libc-start.c。下面是Rust调用ASM,注意引入use core::arch::asm; cargo build的时候会有两个警告。mut是变量,结果为8。
use core::arch::asm;
fn main() {
let mut x: u32 = 5;
unsafe {
asm!(
"add {0}, {1}",
inout(reg) x,
in(reg) 3
);
}
println!("Result: {}", x);
}
.NET9调用Rust lib,如下命令
#cargo new hello-rust --lib 创建一个lib项目
#cd hello-rust
#vim src/lib.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
把Cargo.toml添加一个Lib项
[lib]
crate-type = ["cdylib"] # 或 "dylib" 根据需要选择
然后Cargo build --relese,生成的.so在hello-rust根目录的target/release下面名称规范:lib+项目名称.so,那么本例是:librust_hello.so。新建一个NET9项目,把librust_hello.so复制到bin/Debug/net9.0/,Program.cs如下:
using System;
using System.Runtime.InteropServices;
class Program
{
[ ]
public static extern int add(int a, int b);
static void Main()
{
int result = add(10, 20);
Console.WriteLine($"Result: {result}"); // 输出: Result: 30
}
}
往期精彩回顾