日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

编译调试 .NET Core 5.0 Preview 并分析 Span 的实现原理

發(fā)布時(shí)間:2023/12/4 asp.net 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编译调试 .NET Core 5.0 Preview 并分析 Span 的实现原理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

很久沒(méi)有寫過(guò) .NET Core 相關(guān)的文章了,目前關(guān)店在家休息所以有些時(shí)間寫一篇新的????。這次的文章主要介紹如何在 Linux 上編譯調(diào)試最新的 .NET Core 5.0 Preview 與簡(jiǎn)單分析 Span 的實(shí)現(xiàn)原理。微軟從 .NET Core 5.0 開(kāi)始把 GIT 倉(cāng)庫(kù) coreclr 與 corefx 合并移動(dòng)到了 runtime 倉(cāng)庫(kù),原有倉(cāng)庫(kù)僅用于維護(hù) .NET Core 3.x,你可以從以下地址查看最新的源代碼:

https://github.com/dotnet/runtime

為了方便重現(xiàn),接下來(lái)的編譯調(diào)試會(huì)使用 docker 與 ubuntu 18.04 鏡像(盡管微軟提供了編譯專用的鏡像但并不適合調(diào)試分析),步驟會(huì)與之前的博客介紹的 1.1,書(shū)籍介紹的 2.1 有一些不同。

如果你覺(jué)得閱讀這篇文章有困難,可以參考我之前發(fā)布的 .NET Core 源代碼分析系列或者書(shū)籍《.NET Core 底層入門》,書(shū)籍的購(gòu)買鏈接在文章最后。

編譯 .NET Core 5.0 Preview

本文編譯的版本是 0d607a757372e3ecc8e942141d7f586a98694e42

創(chuàng)建 docker 容器

執(zhí)行以下命令即可創(chuàng)建一個(gè) ubuntu 18.04 的 docker 容器,注意創(chuàng)建時(shí)需要使用 --privileged 參數(shù),否則無(wú)法使用 lldb 或者 gdb 調(diào)試程序。

docker run -it --privileged ubuntu:18.04

安裝 cmake

.NET Core 5.0 要求的 cmake 版本非常高,我們需要添加第三方源來(lái)安裝新版本的 cmake:

apt-get update apt-get install apt-transport-https ca-certificates gnupg software-properties-common wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | apt-key add - apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' apt-get update

安裝依賴的類庫(kù)與工具

這個(gè)步驟與之前版本的 .NET Core 相同:

apt-get install git wget locales locales-all vim apt-get install cmake llvm-3.9 clang-9 libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev libnuma-dev libkrb5-dev

下載 .NET Core 源代碼并編譯

這個(gè)步驟也與之前的 .NET Core 相同,但因?yàn)?corefx 合并到了同一個(gè)倉(cāng)庫(kù)中,執(zhí)行以下步驟以后會(huì)同時(shí)編譯 corefx 的 dll 文件。注意這個(gè)步驟編譯的是 Debug 版本的運(yùn)行時(shí),方便后面的調(diào)試。

git clone https://github.com/dotnet/runtime cd runtime ./build.sh

編譯完成后你可以在 artifacts 文件夾下找到編譯結(jié)果。

使用 .NET Core 5.0 Preview 執(zhí)行 Hello World 程序

接下來(lái)我們會(huì)看如何使用自己編譯的 .NET Core 執(zhí)行一個(gè) Hello World 程序,.NET Core 5.0 會(huì)同時(shí)編譯出 dotnet 程序,我們可以使用它代替 corerun 來(lái)簡(jiǎn)化運(yùn)行步驟(不需要像以前的版本一樣手動(dòng)復(fù)制 corefx 的 dll或者設(shè)置?CORE_ROOT?環(huán)境變量)。但因?yàn)?runtime 倉(cāng)庫(kù)中不包括 sdk(sdk 在 sdk 倉(cāng)庫(kù)中,這次懶得編譯),我們?nèi)匀恍枰硗獍惭b一個(gè)官方的 .NET Core 用于創(chuàng)建與編譯 Hello World 程序。

安裝官方的 .NET Core 3.1 SDK

wget -q https://packages.microsoft.com/config/ubuntu/19.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb dpkg -i packages-microsoft-prod.deb apt-get update apt-get install dotnet-sdk-3.1

創(chuàng)建與編譯 Hello World 程序

mkdir /console cd /console dotnet new console dotnet build

執(zhí)行 Hello World 程序

因?yàn)槭褂昧?.NET Core 3.1 的 SDK 編譯,我們還需要修改?程序名.runtimeconfig.json?中的運(yùn)行時(shí)版本號(hào),否則會(huì)出現(xiàn)版本號(hào)不一致而執(zhí)行失敗的問(wèn)題。

cd /console/bin/Debug/netcoreapp3.1 vi console.runtimeconfig.json

需要修改兩處:

  • runtimeOptions.tfm?修改到?netcoreapp5.0

  • runtimeOptions.framework.version?修改到?5.0.0

修改完以后使用以下命令即可執(zhí)行:

/runtime/artifacts/bin/testhost/netcoreapp5.0-Linux-Debug-x64/dotnet console.dll

如果看到 Hello World 輸出就代表執(zhí)行成功了。

調(diào)試 .NET Core 5.0 Preview

在 linux 上調(diào)試 .NET Core 一般使用 lldb (gdb 也可以但是沒(méi)有 SOS 插件支持),SOS 插件的源代碼被搬到了 diagnostics 倉(cāng)庫(kù),所以我們還需要下載編譯這個(gè)倉(cāng)庫(kù)的源代碼。

下載編譯 diagnostics 倉(cāng)庫(kù) (LLDB SOS 插件)

安裝 LLDB 與 LLDB 的開(kāi)發(fā)文件:

apt-get install clang llvm lldb liblldb-3.9-dev

下載編譯 diagnostics 倉(cāng)庫(kù):

git clone https://github.com/dotnet/diagnostics cd diagnostics ./build.sh

編譯成功后你可以在?/diagnostics/artifacts/bin/Linux.x64.Debug/libsosplugin.so?找到 SOS 插件的 dll 文件。

使用 LLDB 調(diào)試 .NET Core

SOS 插件需要在執(zhí)行到達(dá) LoadLibraryExW 后才可以正常使用,使用 LLDB 的 -o 參數(shù)可以省略每次調(diào)試的時(shí)候都要做的準(zhǔn)備工作:

cd /console/bin/Debug/netcoreapp3.1 lldb \-o "plugin load /diagnostics/artifacts/bin/Linux.x64.Debug/libsosplugin.so" \-o "process launch -s" \-o "process handle -s false SIGUSR1 SIGUSR2" \-o "b LoadLibraryExW" \-o "c" \-o "br del 1" \-o "sos Help" \/runtime/artifacts/bin/testhost/netcoreapp5.0-Linux-Debug-x64/dotnet console.dll

執(zhí)行以后會(huì)停在 LoadLibraryExW 并打印出 SOS 插件的幫助,接下來(lái)我們可以使用 SOS 插件給托管函數(shù)下斷點(diǎn):

sos bpmd console.dll console.Program.Main

然后使用 c 命令繼續(xù)執(zhí)行程序,直到觸發(fā)斷點(diǎn):

c

到達(dá)斷點(diǎn)(JIT 編譯后的托管函數(shù) Main)以后我們可以使用 SOS 插件打印這個(gè)托管函數(shù)編譯出來(lái)的匯編內(nèi)容:

sos u $rip

如果到此都沒(méi)有問(wèn)題,那么接下來(lái)我們可以開(kāi)始分析 Span 的實(shí)現(xiàn)原理了。

Span 與 Memory 簡(jiǎn)介

Span 與 Memory 是微軟推出的,用于表示某段子內(nèi)容的數(shù)據(jù)類型,它們的主要目的是為了減少內(nèi)存分配與復(fù)制,例如取 "abcdefg" 的子字符串 "def",傳統(tǒng)的方法 (Substring) 會(huì)分配一個(gè)長(zhǎng)度為 3 的新字符串然后復(fù)制 "def" 過(guò)去,但 Span 與 Memory 可以直接使用原有的對(duì)象、子內(nèi)容的開(kāi)始位置與子內(nèi)容的長(zhǎng)度來(lái)表示一段子內(nèi)容。在其他語(yǔ)言中也有類似 Span 與 Memory 的概念,例如 go 中的 slice,c 中指針與長(zhǎng)度的結(jié)合 (例如?struct char_view { char* ptr, size_t size; }),與 c++ 中的?string_view?和?span?類型。

Span 與 Memory 的區(qū)別在于,Memory 是一個(gè)普通的類型,只保存?原有的對(duì)象、子內(nèi)容的開(kāi)始地址?與?子內(nèi)容的長(zhǎng)度,在內(nèi)存中的表現(xiàn)可以參考下圖:

Memory 與很早就存在的 ArraySegment 實(shí)質(zhì)上是一樣的,只是支持更多的類型,它們都不需要運(yùn)行時(shí)或者編譯器的額外支持。

Span 則特殊很多,它保存了子內(nèi)容的開(kāi)始地址與長(zhǎng)度(不保存原始對(duì)象的地址),使得它不需要計(jì)算開(kāi)始地址并且允許指向托管對(duì)象以外的內(nèi)容 (例如從 stackalloc 分配)。Span 在內(nèi)存中的表現(xiàn)可以參考下圖:

Span 是一個(gè)?ref struct?類型 (這個(gè)類型可以說(shuō)是專門為 Span 發(fā)明的),ref struct?只能保存在于棧上或者作為其他?ref struct?的成員 (最終來(lái)說(shuō)只能保存在于棧上),Span 只能存在于棧上主要有以下原因:

  • GC 處理 Span 對(duì)象的成本很高,所以不應(yīng)該大范圍使用

  • Span 的讀寫是非原子的(兩個(gè)指針大小),如果允許在堆上就有可能被多個(gè)線程同時(shí)訪問(wèn)

  • Span 可以由 stackalloc 生成,而 Span 自身并不會(huì)標(biāo)記來(lái)源是托管對(duì)象還是棧空間

因?yàn)?Span 需要運(yùn)行時(shí)的額外支持,在 .NET Framework 與 Mono 上使用的 Span (從 Nuget 包安裝的) 實(shí)際上與 Memory 一樣,只有在 .Net Core 上才有以上的特性。

此外,因?yàn)椴糠謱?duì)象的內(nèi)容不可修改 (例如 string),所以還有配套的?ReadOnlySpan?與?ReadOnlyMemory,它們除了在編譯器層面上限制修改以外,與原類型沒(méi)有什么區(qū)別。

調(diào)試分析 Span 的實(shí)現(xiàn)原理

接下來(lái)我們可以調(diào)試一個(gè)示例程序,簡(jiǎn)單分析 Span 在運(yùn)行時(shí)中的實(shí)現(xiàn)原理 (這次分析不涉及到 JIT 部分,雖然 JIT 部分很少)。

以下是示例程序的代碼:

using System;namespace console {class Program{static void Main(string[] args){Span<byte> span = new byte[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };span = span.Slice(5, 2);GC.Collect();Console.WriteLine(span.Length);}} }

使用 LLDB 查看生成的匯編代碼

編譯示例程序與執(zhí)行 LLDB 的命令請(qǐng)參考前面的內(nèi)容,執(zhí)行后可以使用以下命令給托管函數(shù)?Main?下斷點(diǎn)然后執(zhí)行到斷點(diǎn),并查看匯編代碼:

sos bpmd console.dll console.Program.Main c sos u $rip

輸出如下:

(lldb) sos bpmd console.dll console.Program.Main Adding pending breakpoints... (lldb) c Process 6460 resuming JITTED console!console.Program.Main(System.String[]) Setting breakpoint: breakpoint set --address 0x00007FFF7BB352D0 [console.Program.Main(System.String[])] Process 6460 stopped * thread #1, name = 'dotnet', stop reason = breakpoint 3.1frame #0: 0x00007fff7bb352d0 -> 0x7fff7bb352d0: pushq %rbp0x7fff7bb352d1: pushq %r130x7fff7bb352d3: subq $0x48, %rsp0x7fff7bb352d7: vzeroupper (lldb) sos u $rip Normal JIT generated code console.Program.Main(System.String[]) ilAddr is 00007FFFF18BB250 pImport is 00005576894771F0 Begin 00007FFF7BB352D0, size bc/console/Program.cs @ 9: >>> 00007fff7bb352d0 55 push rbp 00007fff7bb352d1 4155 push r13 00007fff7bb352d3 4883ec48 sub rsp, 0x48 00007fff7bb352d7 c5f877 vzeroupper 00007fff7bb352da 488d6c2450 lea rbp, [rsp + 0x50] 00007fff7bb352df 4c8bef mov r13, rdi 00007fff7bb352e2 488d7db0 lea rdi, [rbp - 0x50] 00007fff7bb352e6 b910000000 mov ecx, 0x10 00007fff7bb352eb 33c0 xor eax, eax 00007fff7bb352ed f3ab rep stosd dword ptr es:[rdi], eax 00007fff7bb352ef 498bfd mov rdi, r13 00007fff7bb352f2 48897df0 mov qword ptr [rbp - 0x10], rdi 00007fff7bb352f6 48bfe05fd87bff7f0000 movabs rdi, 0x7fff7bd85fe0 00007fff7bb35300 be0a000000 mov esi, 0xa 00007fff7bb35305 e8063fe079 call 0x7ffff5939210 (JitHelp: CORINFO_HELP_NEWARR_1_VC) 00007fff7bb3530a 488945d8 mov qword ptr [rbp - 0x28], rax 00007fff7bb3530e 48bf2894e07bff7f0000 movabs rdi, 0x7fff7be09428 00007fff7bb35318 e8b396e079 call 0x7ffff593e9d0 (JitHelp: CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD) 00007fff7bb3531d 488945d0 mov qword ptr [rbp - 0x30], rax 00007fff7bb35321 488b7dd8 mov rdi, qword ptr [rbp - 0x28] 00007fff7bb35325 488b75d0 mov rsi, qword ptr [rbp - 0x30] 00007fff7bb35329 e8829f307a call 0x7ffff5e3f2b0 (System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle), mdToken: 0000000006003730) 00007fff7bb3532e 488b7dd8 mov rdi, qword ptr [rbp - 0x28] 00007fff7bb35332 e8f9ecffff call 0x7fff7bb34030 (System.Span`1[[System.Byte, System.Private.CoreLib]].op_Implicit(Byte[]), mdToken: 00000000060012B1) 00007fff7bb35337 488945c0 mov qword ptr [rbp - 0x40], rax 00007fff7bb3533b 488955c8 mov qword ptr [rbp - 0x38], rdx 00007fff7bb3533f c5fa6f45c0 vmovdqu xmm0, xmmword ptr [rbp - 0x40] 00007fff7bb35344 c5fa7f45e0 vmovdqu xmmword ptr [rbp - 0x20], xmm0/console/Program.cs @ 10: 00007fff7bb35349 488d7de0 lea rdi, [rbp - 0x20] 00007fff7bb3534d be05000000 mov esi, 0x5 00007fff7bb35352 ba02000000 mov edx, 0x2 00007fff7bb35357 e844edffff call 0x7fff7bb340a0 (System.Span`1[[System.Byte, System.Private.CoreLib]].Slice(Int32, Int32), mdToken: 00000000060012BE) 00007fff7bb3535c 488945b0 mov qword ptr [rbp - 0x50], rax 00007fff7bb35360 488955b8 mov qword ptr [rbp - 0x48], rdx 00007fff7bb35364 c5fa6f45b0 vmovdqu xmm0, xmmword ptr [rbp - 0x50] 00007fff7bb35369 c5fa7f45e0 vmovdqu xmmword ptr [rbp - 0x20], xmm0/console/Program.cs @ 11: 00007fff7bb3536e e845b3ffff call 0x7fff7bb306b8 (System.GC.Collect(), mdToken: 0000000006000361)/console/Program.cs @ 12: 00007fff7bb35373 488d7de0 lea rdi, [rbp - 0x20] 00007fff7bb35377 e87cecffff call 0x7fff7bb33ff8 (System.Span`1[[System.Byte, System.Private.CoreLib]].get_Length(), mdToken: 00000000060012AC) 00007fff7bb3537c 8bf8 mov edi, eax 00007fff7bb3537e e8a5fcffff call 0x7fff7bb35028 (System.Console.WriteLine(Int32), mdToken: 0000000006000089)/console/Program.cs @ 13: 00007fff7bb35383 90 nop 00007fff7bb35384 488d65f8 lea rsp, [rbp - 0x8] 00007fff7bb35388 415d pop r13 00007fff7bb3538a 5d pop rbp 00007fff7bb3538b c3 ret

我們可以看到 00007fff7bb35305 處的指令從托管堆分配了數(shù)組,00007fff7bb35329 處的指令初始化了數(shù)組內(nèi)容,00007fff7bb35332 處的指令生成了第一個(gè) span 對(duì)象,00007fff7bb35357 處的指令生成了第二個(gè) span 對(duì)象。你可以從每一段匯編代碼上標(biāo)記的文件名與行數(shù)找到對(duì)應(yīng)的 C# 代碼。

分析棧上的內(nèi)容

接下來(lái)我們會(huì)分析棧上的內(nèi)容,包括數(shù)組的地址與 span 的內(nèi)容等。

注意棧上會(huì)保存臨時(shí)變量和不使用的參數(shù),這是因?yàn)橹暗木幾g沒(méi)有使用 Release 配置,你可以使用 Release 配置編譯再按這里的步驟試試有什么不同 (可能會(huì)更難理解一些),使用 Release 配置時(shí)請(qǐng)關(guān)閉分層編譯,使用?export COMPlus_TieredCompilation=0?即可關(guān)閉。

首先我們來(lái)看看分配數(shù)組之前棧上 (當(dāng)前幀) 有什么內(nèi)容:

(lldb) b 0x00007fff7bb35305 Breakpoint 4: address = 0x00007fff7bb35305 # 分配數(shù)組的指令 (lldb) c Process 6460 resuming Process 6460 stopped * thread #1, name = 'dotnet', stop reason = breakpoint 4.1frame #0: 0x00007fff7bb35305 -> 0x7fff7bb35305: callq 0x7ffff5939210 ; JIT_NewArr1VC_MP_FastPortable at jithelpers.cpp:25600x7fff7bb3530a: movq %rax, -0x28(%rbp)0x7fff7bb3530e: movabsq $0x7fff7be09428, %rdi ; imm = 0x7FFF7BE094280x7fff7bb35318: callq 0x7ffff593e9d0 ; JIT_GetRuntimeFieldStub at jithelpers.cpp:3635 (lldb) p/x $rsp (unsigned long) $2 = 0x00007fffffffd220 # 棧頂 (lldb) p/x $rbp (unsigned long) $3 = 0x00007fffffffd270 # 幀底 (lldb) p $rbp - $rsp (unsigned long) $4 = 80 # 當(dāng)前幀大小 (lldb) memory read -s 1 -c 80 0x00007fffffffd220 0x7fffffffd220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ # 本地變量使用的空間 0x7fffffffd230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ # 本地變量使用的空間 0x7fffffffd240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ # 本地變量使用的空間 0x7fffffffd250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ # 本地變量使用的空間 0x7fffffffd260: b0 d5 00 54 ff 7f 00 00 00 00 00 00 00 00 00 00 ...T............ # rbp-0x10 是 args 參數(shù),rbp-0x8 是上一幀 r13 的值

接下來(lái)我們看看原始數(shù)組的地址與數(shù)組的內(nèi)容,數(shù)組的本地變量 (臨時(shí)變量) 會(huì)保存到?$rbp-0x28,我們可以直接看這個(gè)地址中的內(nèi)容。

(lldb) b 0x00007fff7bb3532e Breakpoint 5: address = 0x00007fff7bb3532e # 初始化數(shù)組后的指令 (lldb) c Process 6460 resuming Process 6460 stopped * thread #1, name = 'dotnet', stop reason = breakpoint 5.1frame #0: 0x00007fff7bb3532e -> 0x7fff7bb3532e: movq -0x28(%rbp), %rdi0x7fff7bb35332: callq 0x7fff7bb340300x7fff7bb35337: movq %rax, -0x40(%rbp)0x7fff7bb3533b: movq %rdx, -0x38(%rbp) (lldb) p/x $rbp-0x28 (unsigned long) $6 = 0x00007fffffffd248 (lldb) memory read -s 1 -c 8 0x00007fffffffd248 0x7fffffffd248: 70 ed 00 54 ff 7f 00 00 p..T.... (lldb) dumpobj 7fff5400ed70 # SOS 插件提供的命令,用于輸出托管對(duì)象信息 Name: System.Byte[] MethodTable: 00007fff7bd85fe0 EEClass: 00007fff7bd85f30 Size: 34(0x22) bytes Array: Rank 1, Number of elements 10, Type Byte Content: .......... Fields: None (lldb) memory read -s 1 -c 26 0x7fff5400ed70 # 顯示數(shù)組對(duì)象的內(nèi)容 0x7fff5400ed70: e0 5f d8 7b ff 7f 00 00 0a 00 00 00 00 00 00 00 ._.{............ # 0~8 是類型信息,8~16 是長(zhǎng)度 0x7fff5400ed80: 01 02 03 04 05 06 07 08 09 0a .......... # 16~26 是數(shù)組內(nèi)容

接下來(lái)我們可以繼續(xù)執(zhí)行,然后看看各個(gè) Span 的內(nèi)容:

(lldb) b 0x00007fff7bb3536e Breakpoint 6: address = 0x00007fff7bb3536e (lldb) c Process 6460 resuming Process 6460 stopped * thread #1, name = 'dotnet', stop reason = breakpoint 6.1frame #0: 0x00007fff7bb3536e -> 0x7fff7bb3536e: callq 0x7fff7bb306b80x7fff7bb35373: leaq -0x20(%rbp), %rdi0x7fff7bb35377: callq 0x7fff7bb33ff80x7fff7bb3537c: movl %eax, %edi (lldb) memory read -s 1 -c 16 $rbp-0x40 0x7fffffffd230: 80 ed 00 54 ff 7f 00 00 0a 00 00 00 00 00 00 00 ...T............ # 第一個(gè) span (臨時(shí)變量) 的開(kāi)始地址與長(zhǎng)度 (lldb) memory read -s 1 -c 16 $rbp-0x50 0x7fffffffd220: 85 ed 00 54 ff 7f 00 00 02 00 00 00 00 00 00 00 ...T............ # 第二個(gè) span (臨時(shí)變量) 的開(kāi)始地址與長(zhǎng)度 (lldb) memory read -s 1 -c 16 $rbp-0x20 0x7fffffffd250: 85 ed 00 54 ff 7f 00 00 02 00 00 00 00 00 00 00 ...T............ # 本地變量 span 中的開(kāi)始地址與長(zhǎng)度

從輸出中我們可以看到,第一個(gè) span 的地址是 0x7fff5400ed80,這剛好是數(shù)組地址 0x7fff5400ed70 加上類型信息 (8) 與長(zhǎng)度 (8) 以后的值,
也就是數(shù)組的內(nèi)容,使用以下命令可以查看這個(gè) span 指向的內(nèi)容:

(lldb) memory read -s 1 -c 10 0x7fff5400ed80 0x7fff5400ed80: 01 02 03 04 05 06 07 08 09 0a ..........

而第二個(gè) span 的地址 0x7fff5400ed85 則是第一個(gè) span 的地址加 5,并且長(zhǎng)度為 2,使用以下命令可以查看這個(gè) span 指向的內(nèi)容:

(lldb) memory read -s 1 -c 2 0x7fff5400ed85 0x7fff5400ed85: 06 07 ..

最后再看看棧上 (當(dāng)前幀) 的內(nèi)容:

(lldb) memory read -s 1 -c 80 0x00007fffffffd220 0x7fffffffd220: 85 ed 00 54 ff 7f 00 00 02 00 00 00 00 00 00 00 ...T............ # 本地變量 span 中的開(kāi)始地址與長(zhǎng)度 0x7fffffffd230: 80 ed 00 54 ff 7f 00 00 0a 00 00 00 00 00 00 00 ...T............ # 第一個(gè) span (臨時(shí)變量) 的開(kāi)始地址與長(zhǎng)度 0x7fffffffd240: 98 ed 00 54 ff 7f 00 00 70 ed 00 54 ff 7f 00 00 ...T....p..T.... # 用于初始化數(shù)組的句柄,原始數(shù)組對(duì)象 (臨時(shí)變量) 0x7fffffffd250: 85 ed 00 54 ff 7f 00 00 02 00 00 00 00 00 00 00 ...T............ # 第二個(gè) span (臨時(shí)變量) 的開(kāi)始地址與長(zhǎng)度 0x7fffffffd260: b0 d5 00 54 ff 7f 00 00 00 00 00 00 00 00 00 00 ...T............ # args 參數(shù)與上一幀 r13 的值

查看托管函數(shù)對(duì)應(yīng) GC 信息中的各個(gè) Slot

GC 信息是 .NET 運(yùn)行時(shí)查找各個(gè)線程中托管函數(shù)的本地變量 (根對(duì)象) 時(shí)使用的信息,因?yàn)?GC 信息的編碼非常復(fù)雜,這里不會(huì)介紹如何解碼 GC 信息,
而是下斷點(diǎn)來(lái)看各個(gè) Slot 的內(nèi)容,從掃描到標(biāo)記的調(diào)用鏈跟蹤 (backtrace) 如下:

* frame #0: 0x00007ffff5cb0fcf libcoreclr.so`WKS::gc_heap::mark_object_simple(po=0x00007fffffffa460) at gc.cpp:19675frame #1: 0x00007ffff5cb6fe8 libcoreclr.so`WKS::GCHeap::Promote(ppObject=0x00007fffffffd230, sc=0x00007fffffffc9c0, flags=1) at gc.cpp:36730frame #2: 0x00007ffff5808fe8 libcoreclr.so`PromoteCarefully(fn=(libcoreclr.so`WKS::GCHeap::Promote(Object**, ScanContext*, unsigned int) at gc.cpp:36666), ppObj=0x00007fffffffd230, sc=0x00007fffffffc9c0, flags=1)(Object**, ScanContext*, unsigned int), Object**, ScanContext*, unsigned int) at siginfo.cpp:4874frame #3: 0x00007ffff5918c4a libcoreclr.so`GcEnumObject(pData=0x00007fffffffc710, pObj=0x00007fffffffd230, flags=1) at gcenv.ee.common.cpp:167frame #4: 0x00007ffff5a87abc libcoreclr.so`GcInfoDecoder::ReportStackSlotToGC(this=0x00007fffffffab38, spOffset=-80, spBase=GC_FRAMEREG_REL, gcFlags=1, pRD=0x00007fffffffb5c0, flags=0, pCallBack=(libcoreclr.so`GcEnumObject(void*, OBJECTREF*, unsigned int) at gcenv.ee.common.cpp:148), hCallBack=0x00007fffffffc710)(void*, OBJECTREF*, unsigned int), void*) at gcinfodecoder.cpp:1848frame #5: 0x00007ffff5a88381 libcoreclr.so`GcInfoDecoder::ReportSlotToGC(this=0x00007fffffffab38, slotDecoder=0x00007fffffffa8d0, slotIndex=0, pRD=0x00007fffffffb5c0, reportScratchSlots=true, inputFlags=0, pCallBack=(libcoreclr.so`GcEnumObject(void*, OBJECTREF*, unsigned int) at gcenv.ee.common.cpp:148), hCallBack=0x00007fffffffc710)(void*, OBJECTREF*, unsigned int), void*) at gcinfodecoder.h:679frame #6: 0x00007ffff5a8666d libcoreclr.so`GcInfoDecoder::ReportUntrackedSlots(this=0x00007fffffffab38, slotDecoder=0x00007fffffffa8d0, pRD=0x00007fffffffb5c0, inputFlags=0, pCallBack=(libcoreclr.so`GcEnumObject(void*, OBJECTREF*, unsigned int) at gcenv.ee.common.cpp:148), hCallBack=0x00007fffffffc710)(void*, OBJECTREF*, unsigned int), void*) at gcinfodecoder.cpp:1034frame #7: 0x00007ffff5a85d28 libcoreclr.so`GcInfoDecoder::EnumerateLiveSlots(this=0x00007fffffffab38, pRD=0x00007fffffffb5c0, reportScratchSlots=false, inputFlags=0, pCallBack=(libcoreclr.so`GcEnumObject(void*, OBJECTREF*, unsigned int) at gcenv.ee.common.cpp:148), hCallBack=0x00007fffffffc710)(void*, OBJECTREF*, unsigned int), void*) at gcinfodecoder.cpp:983frame #8: 0x00007ffff570225a libcoreclr.so`EECodeManager::EnumGcRefs(this=0x0000555555822680, pRD=0x00007fffffffb5c0, pCodeInfo=0x00007fffffffb3f0, flags=0, pCallBack=(libcoreclr.so`GcEnumObject(void*, OBJECTREF*, unsigned int) at gcenv.ee.common.cpp:148), hCallBack=0x00007fffffffc710, relOffsetOverride=4294967295)(void*, OBJECTREF*, unsigned int), void*, unsigned int) at eetwain.cpp:5150frame #9: 0x00007ffff5919462 libcoreclr.so`GcStackCrawlCallBack(pCF=0x00007fffffffb1c0, pData=0x00007fffffffc710) at gcenv.ee.common.cpp:283frame #10: 0x00007ffff580e52f libcoreclr.so`Thread::MakeStackwalkerCallback(this=0x0000555555838aa0, pCF=0x00007fffffffb1c0, pCallback=(libcoreclr.so`GcStackCrawlCallBack(CrawlFrame*, void*) at gcenv.ee.common.cpp:201), pData=0x00007fffffffc710, uFramesProcessed=5)(CrawlFrame*, void*), void*, unsigned int) at stackwalk.cpp:886frame #11: 0x00007ffff580e77b libcoreclr.so`Thread::StackWalkFramesEx(this=0x0000555555838aa0, pRD=0x00007fffffffb5c0, pCallback=(libcoreclr.so`GcStackCrawlCallBack(CrawlFrame*, void*) at gcenv.ee.common.cpp:201), pData=0x00007fffffffc710, flags=34048, pStartFrame=0x0000000000000000)(CrawlFrame*, void*), void*, unsigned int, Frame*) at stackwalk.cpp:966frame #12: 0x00007ffff580f337 libcoreclr.so`Thread::StackWalkFrames(this=0x0000555555838aa0, pCallback=(libcoreclr.so`GcStackCrawlCallBack(CrawlFrame*, void*) at gcenv.ee.common.cpp:201), pData=0x00007fffffffc710, flags=34048, pStartFrame=0x0000000000000000)(CrawlFrame*, void*), void*, unsigned int, Frame*) at stackwalk.cpp:1049frame #13: 0x00007ffff5ceeadb libcoreclr.so`ScanStackRoots(pThread=0x0000555555838aa0, fn=(libcoreclr.so`WKS::GCHeap::Promote(Object**, ScanContext*, unsigned int) at gc.cpp:36666), sc=0x00007fffffffc9c0)(Object**, ScanContext*, unsigned int), ScanContext*) at gcenv.ee.cpp:146frame #14: 0x00007ffff5cee7ab libcoreclr.so`GCToEEInterface::GcScanRoots(fn=(libcoreclr.so`WKS::GCHeap::Promote(Object**, ScanContext*, unsigned int) at gc.cpp:36666), condemned=2, max_gen=2, sc=0x00007fffffffc9c0)(Object**, ScanContext*, unsigned int), int, int, ScanContext*) at gcenv.ee.cpp:182frame #15: 0x00007ffff5cfa3d9 libcoreclr.so`GCScan::GcScanRoots(fn=(libcoreclr.so`WKS::GCHeap::Promote(Object**, ScanContext*, unsigned int) at gc.cpp:36666), condemned=2, max_gen=2, sc=0x00007fffffffc9c0)(Object**, ScanContext*, unsigned int), int, int, ScanContext*) at gcscan.cpp:155frame #16: 0x00007ffff5c9f701 libcoreclr.so`WKS::gc_heap::mark_phase(condemned_gen_number=2, mark_only_p=NO) at gc.cpp:21062frame #17: 0x00007ffff5c9b479 libcoreclr.so`WKS::gc_heap::gc1() at gc.cpp:16713frame #18: 0x00007ffff5cab832 libcoreclr.so`WKS::gc_heap::garbage_collect(n=2) at gc.cpp:18345frame #19: 0x00007ffff5c90dea libcoreclr.so`WKS::GCHeap::GarbageCollectGeneration(this=0x0000555555793aa0, gen=2, reason=reason_induced) at gc.cpp:38188frame #20: 0x00007ffff5cdd3bb libcoreclr.so`WKS::GCHeap::GarbageCollectTry(this=0x0000555555793aa0, generation=2, low_memory_p=NO, mode=2) at gc.cpp:37524frame #21: 0x00007ffff5cde614 libcoreclr.so`WKS::GCHeap::GarbageCollect(this=0x0000555555793aa0, generation=2, low_memory_p=false, mode=2) at gc.cpp:37458frame #22: 0x00007ffff58be151 libcoreclr.so`GCInterface::Collect(generation=-1, mode=2) at comutilnative.cpp:986frame #23: 0x00007fff7bb55853frame #24: 0x00007fff7bb55788frame #25: 0x00007fff7bb553c3frame #26: 0x00007ffff5a965f3 libcoreclr.so`CallDescrWorkerInternal at unixasmmacrosamd64.inc:862frame #27: 0x00007ffff589cc9c libcoreclr.so`CallDescrWorkerWithHandler(pCallDescrData=0x00007fffffffd5a8, fCriticalCall=NO) at callhelpers.cpp:70frame #28: 0x00007ffff589da1c libcoreclr.so`MethodDescCallSite::CallTargetWorker(this=0x00007fffffffd6e0, pArguments=0x00007fffffffd680, pReturnValue=0x0000000000000000, cbReturnValue=0) at callhelpers.cpp:546frame #29: 0x00007ffff56ee983 libcoreclr.so`MethodDescCallSite::Call(this=0x00007fffffffd6e0, pArguments=0x00007fffffffd680) at callhelpers.h:459frame #30: 0x00007ffff5ac1c64 libcoreclr.so`RunMainInternal(pParam=0x00007fffffffd950) at assembly.cpp:1487frame #31: 0x00007ffff5ac1989 libcoreclr.so`RunMain(this=0x00007fffffffd858, pParam=0x00007fffffffd950)::$_1::operator()(Param*) const::'lambda'(Param*)::operator()(Param*) const at assembly.cpp:1559frame #32: 0x00007ffff5abf1f9 libcoreclr.so`RunMain(this=0x00007fffffffd940, __EXparam=0x00007fffffffd950)::$_1::operator()(Param*) const at assembly.cpp:1561frame #33: 0x00007ffff5abf019 libcoreclr.so`RunMain(pFD=0x00007fff7bd5c368, numSkipArgs=1, piRetVal=0x00007fffffffda4c, stringArgs=0x00007fffffffdf20) at assembly.cpp:1561frame #34: 0x00007ffff5abf4a2 libcoreclr.so`Assembly::ExecuteMainMethod(this=0x00005555557d4d70, stringArgs=0x00007fffffffdf20, waitForOtherThreads=YES) at assembly.cpp:1671frame #35: 0x00007ffff56e8a6b libcoreclr.so`CorHost2::ExecuteAssembly(this=0x000055555578eb40, dwAppDomainId=1, pwzAssemblyPath=u"/console/bin/Release/netcoreapp3.1/console.dll", argc=0, argv=0x0000000000000000, pReturnValue=0x00007fffffffe100) at corhost.cpp:460frame #36: 0x00007ffff568822a libcoreclr.so`::coreclr_execute_assembly(hostHandle=0x000055555578eb40, domainId=1, argc=0, argv=0x0000000000000000, managedAssemblyPath="/console/bin/Release/netcoreapp3.1/console.dll", exitCode=0x00007fffffffe100) at unixinterface.cpp:407frame #37: 0x00007ffff67dfd8a libhostpolicy.so`___lldb_unnamed_symbol100$$libhostpolicy.so + 810frame #38: 0x00007ffff67e022d libhostpolicy.so`___lldb_unnamed_symbol101$$libhostpolicy.so + 45frame #39: 0x00007ffff67e095b libhostpolicy.so`corehost_main + 203frame #40: 0x00007ffff6a4b73c libhostfxr.so`___lldb_unnamed_symbol204$$libhostfxr.so + 1740frame #41: 0x00007ffff6a49ea1 libhostfxr.so`___lldb_unnamed_symbol202$$libhostfxr.so + 641frame #42: 0x00007ffff6a444f3 libhostfxr.so`hostfxr_main_startupinfo + 147frame #43: 0x00005555555623b7 dotnet`___lldb_unnamed_symbol114$$dotnet + 791frame #44: 0x0000555555562b90 dotnet`___lldb_unnamed_symbol115$$dotnet + 128frame #45: 0x00007ffff6ca3b97 libc.so.6`__libc_start_main + 231frame #46: 0x0000555555557810 dotnet`___lldb_unnamed_symbol9$$dotnet + 41

GcInfoDecoder::EnumerateLiveSlots?是枚舉 Slot 的函數(shù),GcInfoDecoder::ReportSlotToGC?是處理各個(gè) Slot 的函數(shù) (包括寄存器與棧),GcInfoDecoder::ReportStackSlotToGC?是處理?xiàng)I?(引用類型或 ref 類型) 本地變量的函數(shù)。

我們可以在?這個(gè)位置?下斷點(diǎn),然后查看解析出的各個(gè) Slot 的信息:

(lldb) b gcinfodecoder.h:679 Breakpoint 8: where = libcoreclr.so`GcInfoDecoder::ReportSlotToGC(GcSlotDecoder&, unsigned int, REGDISPLAY*, bool, unsigned int, void (*)(void*, OBJECTREF*, unsigned int), void*) + 396 at gcinfodecoder.h:679, address = 0x00007ffff5a8836c (lldb) c Process 6460 resuming Process 6460 stopped * thread #1, name = 'dotnet', stop reason = breakpoint 8.1frame #0: 0x00007ffff5a8836c libcoreclr.so`GcInfoDecoder::ReportSlotToGC(this=0x00007fffffffab28, slotDecoder=0x00007fffffffa8c0, slotIndex=0, pRD=0x00007fffffffb5b0, reportScratchSlots=true, inputFlags=0, pCallBack=(libcoreclr.so`GcEnumObject(void*, OBJECTREF*, unsigned int) at gcenv.ee.common.cpp:148), hCallBack=0x00007fffffffc700)(void*, OBJECTREF*, unsigned int), void*) at gcinfodecoder.h:679676 GcStackSlotBase spBase = pSlot->Slot.Stack.Base;677 if( reportScratchSlots || !IsScratchStackSlot(spOffset, spBase, pRD) )678 { -> 679 ReportStackSlotToGC(680 spOffset,681 spBase,682 pSlot->Flags, (lldb) p *pSlot (const GcSlotDesc) $12 = {Slot = {RegisterNumber = 4294967216Stack = (SpOffset = -80, Base = GC_FRAMEREG_REL)}Flags = GC_SLOT_INTERIOR }

這個(gè) Slot 代表?$rbp-80?($rbp-0x50) 處有引用類型或 ref 類型的本地變量,在前面的內(nèi)容中我們已經(jīng)知道?$rbp-0x50?儲(chǔ)存了第二個(gè) span 對(duì)象,此外標(biāo)志?GC_SLOT_INTERIOR?代表本地變量是對(duì)象中間的內(nèi)存地址,而不是對(duì)象開(kāi)頭(對(duì)象頭之后類型信息之前)的內(nèi)存地址,這個(gè)標(biāo)志會(huì)對(duì) GC 標(biāo)記與重定位對(duì)象產(chǎn)生很大的影響,微軟官方稱這樣的變量為?Interior Pointer。

繼續(xù)執(zhí)行?c?與?p *pSlot?可以看到其他 Slot 的內(nèi)容:

# $rbp-0x40, 即第一個(gè) span 對(duì)象 (const GcSlotDesc) $13 = {Slot = {RegisterNumber = 4294967232Stack = (SpOffset = -64, Base = GC_FRAMEREG_REL)}Flags = GC_SLOT_INTERIOR } # $rbp-0x20, 即本地變量 span (const GcSlotDesc) $14 = {Slot = {RegisterNumber = 4294967264Stack = (SpOffset = -32, Base = GC_FRAMEREG_REL)}Flags = GC_SLOT_INTERIOR } # $rbp-0x30, 用于初始化數(shù)組的句柄 (const GcSlotDesc) $15 = {Slot = {RegisterNumber = 4294967248Stack = (SpOffset = -48, Base = GC_FRAMEREG_REL)}Flags = GC_SLOT_BASE } # $rbp-0x28, 原始數(shù)組對(duì)象 (const GcSlotDesc) $16 = {Slot = {RegisterNumber = 4294967256Stack = (SpOffset = -40, Base = GC_FRAMEREG_REL)}Flags = GC_SLOT_BASE } # $rbp-0x10, args 參數(shù) (const GcSlotDesc) $17 = {Slot = {RegisterNumber = 4294967280Stack = (SpOffset = -16, Base = GC_FRAMEREG_REL)}Flags = GC_SLOT_BASE }

標(biāo)志?GC_SLOT_BASE?代表是普通的引用類型變量,指向?qū)ο蟮拈_(kāi)始地址。

GC 掃描 Span 對(duì)象時(shí)的處理

接下來(lái)我們看看 GC 掃描 Span 對(duì)象時(shí)會(huì)做什么處理,盡管在上述例子中棧上保留了原始數(shù)組的地址,使用 Release 模式編譯時(shí)可能會(huì)出現(xiàn)不保留的情況,因此 .NET Core 的運(yùn)行時(shí)支持根據(jù)對(duì)象中間的地址找到對(duì)象的開(kāi)始地址 (在前幾年已經(jīng)實(shí)現(xiàn)了),重新運(yùn)行程序并使用以下命令可以給標(biāo)記對(duì)象存活的函數(shù)下斷點(diǎn):

(lldb) b GCHeap::Promote Breakpoint 10: 2 locations.

繼續(xù)執(zhí)行到達(dá)斷點(diǎn)以后我們可以從?ppObject?得到標(biāo)記對(duì)象地址的地址,這里的對(duì)象地址是第二個(gè) span 對(duì)象中保存的開(kāi)始地址,同時(shí) flags 為 1 即?GC_CALL_INTERIOR?代表地址為對(duì)象中間的地址:

(lldb) b GCHeap::Promote Breakpoint 2: 2 locations. (lldb) c Process 6636 resuming Process 6636 stopped * thread #1, name = 'dotnet', stop reason = breakpoint 2.1frame #0: 0x00007ffff5cb6dc3 libcoreclr.so`WKS::GCHeap::Promote(ppObject=0x00007fffffffd220, sc=0x00007fffffffc9b0, flags=1) at gc.cpp:3666936666 {36667 THREAD_NUMBER_FROM_CONTEXT;36668 #ifndef MULTIPLE_HEAPS -> 36669 const int thread = 0;36670 #endif //!MULTIPLE_HEAPS3667136672 uint8_t* o = (uint8_t*)*ppObject; (lldb) p/x *((long*)0x00007fffffffd220) (long) $0 = 0x00007fff5400ed85

因?yàn)榈刂吩趯?duì)象中間,.NET Core 運(yùn)行時(shí)需要先找到對(duì)象的開(kāi)始地址才能標(biāo)記對(duì)象存活 (標(biāo)記存活的位是類型信息的最低位),處理的代碼如下 (文件):

#ifdef INTERIOR_POINTERS if (flags & GC_CALL_INTERIOR) {if ((o < hp->gc_low) || (o >= hp->gc_high)){return;}if ( (o = hp->find_object (o, hp->gc_low)) == 0){return;}} #endif //INTERIOR_POINTERS

這里會(huì)先判斷地址是否在托管堆中 (如果是 stackalloc 生成的就不在),然后使用?gc_heap::find_object?來(lái)找到對(duì)象的開(kāi)始地址,find_object?會(huì)先找到中間地址在 Brick 表對(duì)應(yīng)的 Brick,然后找到該 Brick 對(duì)應(yīng)范圍中的第一個(gè)托管對(duì)象,然后一個(gè)個(gè)掃描托管對(duì)象判斷地址屬于哪個(gè)托管對(duì)象,如果找到屬于的托管對(duì)象則使用該對(duì)象的開(kāi)始地址,這是一個(gè)比較昂貴的操作。關(guān)于 Brick 表可以參考我之前寫的文章。

GC 重定位 Span 對(duì)象時(shí)的處理

接下來(lái)我們看看 GC 是怎么重定位 Span 對(duì)象的,先退出 LLDB 然后執(zhí)行以下命令設(shè)置環(huán)境變量,這個(gè)環(huán)境變量可以強(qiáng)制每次 GC 的時(shí)候都啟用壓縮:

export COMPlus_gcForceCompact=1

然后再執(zhí)行 LLDB,給?GCHeap::Relocate?下斷點(diǎn)并執(zhí)行到斷點(diǎn):

(lldb) b GCHeap::Relocate Breakpoint 2: 2 locations. (lldb) c Process 6676 resuming Process 6676 stopped * thread #1, name = 'dotnet', stop reason = breakpoint 2.2frame #0: 0x00007ffff5cb4633 libcoreclr.so`WKS::GCHeap::Relocate(ppObject=0x00007fffffffd220, sc=0x00007fffffffb810, flags=1) at gc.cpp:3674136738 {36739 UNREFERENCED_PARAMETER(sc);36740 -> 36741 uint8_t* object = (uint8_t*)(Object*)(*ppObject);3674236743 THREAD_NUMBER_FROM_CONTEXT;36744 (lldb) p/x *((long*)0x00007fffffffd220) (long) $0 = 0x00007fff5400ed85

同樣的,ppObject?是標(biāo)記對(duì)象地址的地址,flags 為 1 即?GC_CALL_INTERIOR。具體處理代碼如下:

if ((flags & GC_CALL_INTERIOR) && gc_heap::settings.loh_compaction) {if (!((object >= hp->gc_low) && (object < hp->gc_high))){return;}if (gc_heap::loh_object_p (object)){pheader = hp->find_object (object, 0);if (pheader == 0){return;}ptrdiff_t ref_offset = object - pheader;hp->relocate_address(&pheader THREAD_NUMBER_ARG);*ppObject = (Object*)(pheader + ref_offset);return;} }{pheader = object;hp->relocate_address(&pheader THREAD_NUMBER_ARG);*ppObject = (Object*)pheader; }

因?yàn)閴嚎s階段已經(jīng)把對(duì)象內(nèi)容移動(dòng)了,重定位階段只需要修改地址到移動(dòng)后的地址,不管地址是在對(duì)象開(kāi)頭還是在對(duì)象中間,
對(duì)于小對(duì)象并不需要檢查標(biāo)記是否帶有?GC_CALL_INTERIOR,直接找到對(duì)應(yīng)的 Plug (relocate_address?會(huì)再次判斷地址是否在托管堆中),
獲取 Plug 中保存的偏移值,然后讓地址減去該偏移值即可。而大對(duì)象則需要使用?find_object?來(lái)先定位對(duì)象的開(kāi)始地址,以提升處理效率。

至此我們可以發(fā)現(xiàn),因?yàn)?.NET 可以只根據(jù) Span 找到原始對(duì)象并實(shí)現(xiàn)標(biāo)記與重定位,所以 Span 原理上是可以保存在堆上的,但這需要犧牲一定性能支持線程安全與放棄 stackalloc (或者分離到另一個(gè)類型),所以微軟沒(méi)有選擇這么做。

參考鏈接

  • https://github.com/dotnet/runtime

  • https://github.com/dotnet/runtime/blob/master/docs/workflow/building/coreclr/linux-instructions.md

  • https://github.com/dotnet/runtime/blob/0d607a757372e3ecc8e942141d7f586a98694e42/src/libraries/System.Private.CoreLib/src/System/Span.cs

  • https://github.com/dotnet/runtime/blob/0d607a757372e3ecc8e942141d7f586a98694e42/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs

  • https://github.com/dotnet/runtime/blob/0d607a757372e3ecc8e942141d7f586a98694e42/src/libraries/System.Private.CoreLib/src/System/Memory.cs

  • https://github.com/dotnet/runtime/blob/0d607a757372e3ecc8e942141d7f586a98694e42/src/libraries/System.Private.CoreLib/src/System/ReadOnlyMemory.cs

  • https://raw.githubusercontent.com/dotnet/runtime/0d607a757372e3ecc8e942141d7f586a98694e42/src/coreclr/src/gc/gc.cpp

  • https://github.com/dotnet/runtime/blob/0d607a757372e3ecc8e942141d7f586a98694e42/src/coreclr/src/vm/gcinfodecoder.cpp

  • https://github.com/dotnet/runtime/blob/0d607a757372e3ecc8e942141d7f586a98694e42/src/coreclr/src/inc/gcinfodecoder.h

  • https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay

  • https://www.cnblogs.com/zkweb/p/6625049.html

寫在最后

在這里打個(gè)小廣告,我與檸檬????編寫的書(shū)籍《.NET Core 底層入門》在一月份出版了,出版社是北京航空航天大學(xué)出版社,你可以查看以下網(wǎng)站,找到內(nèi)容介紹與購(gòu)買鏈接:

https://netcoreimpl.github.io

或者直接訪問(wèn)京東的購(gòu)買鏈接

https://item.jd.com/12796746.html

最后傳播一下正能量,最近這段時(shí)間大家都不容易,我目前也沒(méi)有收入來(lái)源,但我們?nèi)匀恍枰獢[正心態(tài),相信祖國(guó),支持政府一同抗擊疫情。
中國(guó)加油????????!武漢加油????????!國(guó)有戰(zhàn),召必回,戰(zhàn)必勝????????!

總結(jié)

以上是生活随笔為你收集整理的编译调试 .NET Core 5.0 Preview 并分析 Span 的实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。