从.NET9到Rust

文摘   2024-08-09 10:50   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

前一篇:从.NET9看Golang,里面了解了下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 goroutine1569 // returns to goexit+PCQuantum.1570 TEXT runtime·goexit(SB),NOSPLIT|TOPFRAME,$0-01571         BYTE    $0x90   // NOP1572         CALL    runtime·goexit1(SB)     // does not return1573         // traceback from goexit1 must hit code range of goexit1574         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{    [DllImport("libhello_rust.so", CallingConvention = CallingConvention.Cdecl)]    public static extern int add(int a, int b);    static void Main()    {       int result = add(10, 20);       Console.WriteLine($"Result: {result}"); // 输出: Result: 30    }}

往期精彩回顾

.NET9现代化编程长啥样?

推荐两个.NET JIT顶级安全加密工具


江湖评谈
记录,分享,自由。
 最新文章