Debugging .NET Core app from a command line on Linux - Dots and Brackets: Code Blog

标签: | 发表时间:2018-12-19 09:15 | 作者:
出处:https://codeblog.dotsandbrackets.com

command line debugging

Million years ago, way before the ice age, I was preparing small C++ project for “Unix Programming” university course and at some point had to debug it via command line. That was mind blowing. And surprisingly productive. Apparently, when nothing stands in the way, especially UI, debugging can become incredibly focused.

Since .NET Framework got his cross platform twin brother .NET Core, I was looking forward to repeat the trick and debug .NET Core app on Ubuntu via command line. Few days ago it finally happened and even though it wasn’t a smooth ride, that was quite an interesting experience. So, let’s have look.

Setup

We’ll need Ubuntu, .NET Core SDK, lldbdebugger and a sample app. Late April, 2018 was the month of updates, so now we have shiny new Ubuntu 18.04and . NET Core SDK 2.1 RC1, which finally got its libsosplugin.socompiled against lldb-3.9, so v3.6we had to use for previous .NETs finally can rest in peace. As for demo project, any .NET Core hello-world wannabe with local variables and call stacks will do.

The tools

I’ll install all of that in a VM and here’s Vagrantfile with its provision.shfile to do so:

Vagrantfile
Ruby
1
2
3
4
5
6
7
8
9
Vagrant.configure("2")do|config|
  config.vm.box="ubuntu/bionic64"
 
  config.vm.provider"virtualbox"do|v|
    v.memory=1024
  end
 
  config.vm.provision"shell",path:"provision.sh"
end

provision.sh
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
 
functioninstall_lldb39{
echo"deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.9 main"|tee/etc/apt/sources.list.d/llvm.list
wget-O-http://llvm.org/apt/llvm-snapshot.gpg.key|apt-keyadd-
apt-getupdate-y
apt-getinstall-ylldb-3.9
}
 
functioninstall_ms_package_source{
wget-qO-https://packages.microsoft.com/keys/microsoft.asc|gpg--dearmor>microsoft.asc.gpg
mvmicrosoft.asc.gpg/etc/apt/trusted.gpg.d/
wget-qhttps://packages.microsoft.com/config/ubuntu/18.04/prod.list
mvprod.list/etc/apt/sources.list.d/microsoft-prod.list
}
 
functioninstall_netsdk21rc1{
install_ms_package_source
 
apt-getinstall-yapt-transport-https
apt-getupdate-y
apt-getinstall-ydotnet-sdk-2.1.300-rc1-008673
}
 
functionmain{
install_netsdk21rc1
install_lldb39
}
 
main

The project

It’s very simple. Let’s have a function that returns a number of ticks passed since last measurement. It’s absolutely useless except for the fact that it will have local variables and some arguments to examine later.

Program.cs
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
usingSystem;
 
namespaceconsole
{
  classProgram
  {
    staticvoidMain(string[]args)
    {
      while(true)
      {
        varlastTick=DateTime.Now.Ticks;
        System.Threading.Thread.Sleep(2000);
        varticksElapsed=GetTicksElapsed(lastTick);
      }
    }
 
    staticlongGetTicksElapsed(longlastTicks)
    {
      varcurrentTicks=DateTime.Now.Ticks;
      vardelta=lastTicks-currentTicks;
      returndelta;
    }
  }
}

Attaching debugger

OK, so let’s start the whole thing and try to connect debugger to it:

provision.sh
Shell
1
2
3
4
dotnetbuild
# ...
dotnetbin/Debug/netcoreapp2.1/console.dll&
# [1] 6124

The program is producing no output and running in a background thread, so knowing the process id ( 6124) I can start lldb, load SOS plugin and connect to dotnet process:

Attach debugger
Shell
1
2
3
4
5
6
7
8
find/usr-namelibsosplugin.so# find SOS plugin
# /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.0-rc1/libsosplugin.so
sudolldb-3.9                  # start LLDB
(lldb)pluginload/usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.0-rc1/libsosplugin.so
(lldb)processattach-p6124
# Process 6124 stopped
# * thread #1: tid = 6124, 0x00007fef21a92ed9 libpthread.so.0`__pthread_cond_timedwait + 649, name = 'dotnet', stop reason = signal SIGSTOP
# ...

Nothing difficult. We can make sure that SOS has kicked in by examining e.g. managed threads before moving on to setting up an actual breakpoint:

View managed threads
Shell
1
2
3
4
5
6
7
8
9
10
11
(lldb)clrthreads
# ThreadCount:      2
# UnstartedThread:  0
# BackgroundThread: 1
# PendingThread:    0
# DeadThread:       0
# Hosted Runtime:   no
#                                                                                                         Lock  
#   ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
#    1    1 17ec 00000000022884A0  2020020 Preemptive  00007FEE8002DE50:00007FEE8002FB30 000000000226F620 0     Ukn
#    7    2 17f2 00000000022AB0D0    21220 Preemptive  0000000000000000:0000000000000000 000000000226F620 0     Ukn (Finalizer)

Setting up a breakpoint

For that we can use bpmd command introduced by SOS plugin. The only parameter it needs is a method name or its descriptor address, so in order to set up a breakpoint in a method called  GetTicksElapsed, which is a member of Program class inside of console.dllassembly, here’s what I’d do:

Add a breakpoint
Shell
1
2
3
4
(lldb)bpmdconsole.dllProgram.GetTicksElapsed
MethodDesc=00007FEEA63F57E8
Settingbreakpoint:breakpointset--address0x00007FEEA7061777[console.Program.GetTicksElapsed(Int64)]
Addingpendingbreakpoints...

As a side note, sometimes it might be actually easier to add a breakpoint by method descriptor address instead. Those are all over the place – in call stacks, instruction pointers, in class method tables. For instance, this is the call stack of currently selected thread:

Call stack
Shell
1
2
3
4
5
6
7
8
(lldb)clrstack
# OS Thread Id: 0x17ec (1)
#         Child SP               IP Call Site
# 00007FFE0D679AA8 00007fef21a92ed9 [HelperMethodFrame: 00007ffe0d679aa8] System.Threading.Thread.SleepInternal(Int32)
# 00007FFE0D679BF0 00007FEEA7165ABB System.Threading.Thread.Sleep(Int32)
# 00007FFE0D679C00 00007FEEA70616FC console.Program.Main(System.String[]) [/home/vagrant/console/Program.cs @ 12]
# 00007FFE0D679F10 00007fef2023de1f [GCFrame: 00007ffe0d679f10]
# 00007FFE0D67A310 00007fef2023de1f [GCFrame: 00007ffe0d67a310]

And this how setting up a breakpoint in Main method might look like:

Set a breakpoint by method descriptor
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(lldb)ip2md00007FEEA70616FC
# MethodDesc:   00007feea63f57d8
# Method Name:          console.Program.Main(System.String[])
# Class:                00007feea7131088
# MethodTable:          00007feea63f5800
# mdToken:              0000000006000001
# Module:               00007feea63f43e0
# IsJitted:             yes
# Current CodeAddr:     00007feea7061690
# Code Version History:
#   CodeAddr:           00007feea7061690  (Non-Tiered)
#   NativeCodeVersion:  0000000000000000
# Source file:  /home/vagrant/console/Program.cs @ 12
(lldb)bpmd-md00007feea63f57d8
# MethodDesc = 00007FEEA63F57D8
# Setting breakpoint: breakpoint set --address 0x00007FEEA7061690 [console.Program.Main(System.String[])]

Easy peasy. Now, let’s resume the program with program continue  command and see how it almost immediately stops at  GetLastTicks:

Breakpoint hit
Shell
1
2
3
4
5
6
(lldb)processcontinue
# Process 6124 resuming
(lldb)Process6124stopped
# * thread #1: tid = 6124, 0x00007feea7061777, name = 'dotnet', stop reason = breakpoint 1.1
#     frame #0: 0x00007feea7061777
# ...

Good. Now it’s time to look around.

Examining call stacks

clrstack (or sos ClrStack) does exactly that:

View call stack
Shell
1
2
3
4
5
6
(lldb)clrstack
# OS Thread Id: 0x17ec (1)
#         Child SP               IP Call Site
# 00007FFE0D679BB0 00007FEEA7061777 console.Program.GetTicksElapsed(Int64) [/home/vagrant/console/Program.cs @ 18]
# 00007FFE0D679C00 00007FEEA7061706 console.Program.Main(System.String[]) [/home/vagrant/console/Program.cs @ 13]
# ...

The output is pretty straightforward. Even line numbers and file names are there. But clrstack can do more than that. For instance, it also has -p  parameter, which will include function arguments into the output, and that’s something really, really useful:

View stack arguments
Shell
1
2
3
4
5
6
7
8
9
10
11
(lldb)clrstack-p
# OS Thread Id: 0x17ec (1)
#         Child SP               IP Call Site
# 00007FFE0D679BB0 00007FEEA7061777 console.Program.GetTicksElapsed(Int64) [/home/vagrant/console/Program.cs @ 18]
#     PARAMETERS:
#         lastTicks (0x00007FFE0D679BE0) = 0x08d5bacfae88e0b3
#
# 00007FFE0D679C00 00007FEEA7061706 console.Program.Main(System.String[]) [/home/vagrant/console/Program.cs @ 13]
#     PARAMETERS:
#         args (0x00007FFE0D679C40) = 0x00007fee8001e5c0
# ...

Let’s examine what we’ve got. lastTicks value is 0x8d5bacfae88e0b3, which is 636620323491995827 in decimal, which indeed corresponds to current UTC date:

UTC Date

As for args argument, most likely that’s program’s command line arguments and this is something easy to confirm:

Examine args
Shell
1
2
3
4
5
6
7
8
(lldb)dumpobj0x00007fee8001e5c0
# Name:        System.String[]
# MethodTable: 00007feea6e26308
# EEClass:     00007feea65c64a8
# Size:        24(0x18) bytes
# Array:       Rank 1, Number of elements 0, Type CLASS
# Fields:
# None

Yup, that’s empty array of strings allright.

Examining local variables

Bad news here. clrstack -i is the command to see local variables and their values. However, in lldb-3.9 and  libsosplugin.so, which comes with .NET Core 2.1 RC1, this command immediately causes segmentation fault and crash of  lldbprocess. Haven’t checked it in earlier versions, but here it happens 100% of the time.

Stepping in/over/out

Unlike with WinDBG, it doesn’t look like there are SOS commands for stepping in, out or over the next statement or function. However, there’re still native commands, which will step over assembly instructions, but it’s better than nothing. Especially when we can call clrstackand check where in managed realm we currently are:

Step over
Shell
1
2
3
4
5
6
7
(lldb)next
(lldb)Process6124stopped
# * thread #1: tid = 6124, 0x00007feea7061778, name = 'dotnet', stop reason = instruction step into
#     frame #0: 0x00007feea7061778
# ->  0x7feea7061778: callq  0x7feea6af3cd0
#     0x7feea706177d: movq   %rax, -0x38(%rbp)
#     0x7feea7061781: movq   -0x38(%rbp), %rdi

callq, if I’m not mistaken, is the instruction to execute procedure at given address, so  step will probably jump us somewhere:

Step into
Shell
1
2
3
4
5
6
7
8
9
10
11
(lldb)step  
(lldb)Process6124stopped
# * thread #1: tid = 6124, 0x00007feea6af3cd0, name = 'dotnet', stop reason = instruction step into
#     frame #0: 0x00007feea6af3cd0
# ->  0x7feea6af3cd0: pushq  %rbp
# ...
(lldb)clrstack
# OS Thread Id: 0x17ec (1)
#         Child SP               IP Call Site
# 00007FFE0D679BA8 00007FEEA6AF3CD0 System.DateTime.get_Now()
# 00007FFE0D679BB0 00007FEEA706177D console.Program.GetTicksElapsed(Int64) [/home/vagrant/console/Program.cs @ 19]

Yup, we’re in DateTime.Now‘s getter now. I actually could test where  callq  leads without stepping into the method itself. Checking  callq‘s argument would tell the same story:

Dump IP
Shell
1
2
3
4
(lldb)ip2md0x7feea6af3cd0
# MethodDesc:   00007feea66448c0
# Method Name:          System.DateTime.get_Now()
# ...

Stepping out from current procedure could’ve been easy, if it didn’t jump 2 levels up instead of one every other time I used it. Maybe it has something to do with the fact that real call stack is actually a mixture of managed and unmanaged entries, whereas clrstackcommand shows only managed ones ( -f argument would show all of them). Then, I couldn’t find an alias for  step-out  command and had to use its full form instead: thread step-out.

Conclusion

So this is how debugging a .NET Core app from a command line on Linux feels like. It’s surprisingly hard to find any documentation for it, so probably I missed a command or to. Faulting clrstack -i also doesn’t make the debugging easier. But still it’s really cool to be able to add a breakpoint, see how execution goes and examine call stack parameters – all from a command line, on any machine and for any project. It’s also surprising to see how thin the layer between a managed code and assembly language is. If I was able to convert callq instruction argument to managed method description, maybe I’ll be able to convert CPU register values to managed objects as well. Who knows.

相关 [debugging net core] 推荐:

Debugging .NET Core on Linux with LLDB | RayDBG

- -
The LLDB debugger is conceptually similar to the native Windows debugging tools in that it is a low level and command live driven debugger. Part of the reason the .NET Core team chose the LLDB debugger was for its extensibility points that allowed them to create the SOS plugin which can be used to debug .NET core applications.

Debugging .NET Core app from a command line on Linux - Dots and Brackets: Code Blog

- -
Million years ago, way before the ice age, I was preparing small C++ project for “Unix Programming” university course and at some point had to debug it via command line.

Profiling a .NET Core Application on Linux | All Your Base Are Belong To Us

- -
In the same vein of  my previous post on analyzing core dumps of .NET Core applications on Linux, let’s take a look at what it takes to do some basic performance profiling.

.Net Core 全局性能诊断工具

- - IT瘾-dev
现在.NET Core 上线后,不可避免的会出现各种问题,如内存泄漏、CPU占用高、接口处理耗时较长等问题. 这个时候就需要快速准确的定位问题,并解决. 这时候就可以使用.NET Core 为开发人员提供了一系列功能强大的诊断工具. 接下来就详细了解下:.NET Core 全局诊断工具. dotnet-counters 是一个性能监视工具,用于初级运行状况监视和性能调查.

Analyzing a .NET Core Core Dump on Linux | All Your Base Are Belong To Us

- -
I thought this walkthrough might be useful if you find yourself in the same boat, because, to be quite honest, I didn’t find it trivial.. A lot of distros will have something preconfigured, but the simplest approach is to just put a file name in the /proc/sys/kernel/core_pattern file:.

.Net Core in Docker - 在容器内编译发布并运行 - Agile.Zhou - 博客园

- -
Docker可以说是现在微服务,DevOps的基础,咱们.Net Core自然也得上Docker. .Net Core发布到Docker容器的教程网上也有不少,但是今天还是想来写一写. 你搜.Net core程序发布到Docker网上一般常见的有两种方案:. 1、在本地编译成Dll文件后通过SCP命令或者WinSCP等工具上传到服务器上,然后构建Docker镜像再运行容器.

为什么 web 开发人员需要迁移到. NET Core, 并使用 ASP.NET Core MVC 构建 web 和 webservice/API - 张善友 - 博客园

- -
2018 .NET开发者调查报告: .NET Core 是怎么样的状态,这里我们看到了还有非常多的.net开发人员还在观望,本文给大家一个建议. 这仅代表我的个人意见, 我有充分的理由推荐.net 程序员使用. 有些人可能不同意我的观点, 但是分享想法和讨论它是好的. .net 程序员或他们所在的团队总有各种理由说他们的系统还在使用旧系统, 这显然是企业开发人员的事情.

【实验手册】使用Visual Studio Code 开发.NET Core应用程序 - 张善友 - 博客园

- -
开源和跨平台开发是Microsoft 的当前和将来至关重要的策略. .NET Core已开源,同时开发了其他项来使用和支持新的跨平台策略. .NET Core 2.0 目前已经正式发布,是适用于针对 Web 和云构建跨平台应用程序的最新开源技术,可在 Linux、Mac OS X 和 Windows 上运行.

Javascipt脚本调试(Javascript debugging)

- - 博客园_首页
根据 CNZZ数据中心对国内主流浏览器的统计分析,2012年3月国产浏览器中360安全浏览器、搜狗高速浏览器和傲游浏览器的使用率分别为24.39%、7.37%、1.75%;国外浏览器中微软IE浏览器、谷歌Chrome浏览器及苹果Safari浏览器的使用率分别为53.40%、3.21%、2.22%.

KISSY Core 预览版

- MArCoRQ - 岁月如歌
KISSY 是淘宝新一代前端 UI 类库,陆陆续续经过大半年的开发,终于完成了核心部分. KISSY 借鉴了 YUI3 的代码组织思想,尝试融合 jQuery/YUI2/ExtJS 等类库的优点. 目前才刚起步,下面是相关话题:. 请先看个 ppt, 或许能解答你的疑惑:前端_UI_类库_KISSY_赛马竞标书.pptx.