Building `swift-inspect` on Linux

Hello,

I have tried to build swift-inspect but I have failed to build it.

I have used the provided helper script by invoking

$ ./build_script_helper.py --package-path . --build-path .build --toolchain /usr/local/bin/swift --configuration debug

but it always files on inability to import SwiftRemoteMirror.

/home/mikolas/Developer/swift/tools/swift-inspect/Sources/swift-inspect/Backtrace.swift:13:8: error: no such module 'SwiftRemoteMirror'
import SwiftRemoteMirror
       ^

I have tried (and failed) to play with SwiftRemoteMirror before.

In the script, I have noticed some things that bother me. For example the -I ${toolchain}/usr/include/swift directory does not exist in the toolchain (the swift directory is missing). And the -L ${toolchain}/usr/lib/swift/macosx directory is also missing - there is linux directory instead of macosx.

The linux directory, however, contains libswiftRemoteMirror.so, which made me think, that I have invoked the build incorrectly. On second thought, there should be some kind of header file or .swiftmodule "file" which is not the case.

Can I build and use swift-inspect using Linux (Ubuntu 20.04) RELEASE or Development SNAPSHOT toolchain? Or do I need to build by own toolchain from source?

Thanks :)

Note: The swift-inspect package I have tried to build is from apple/swift main branch.
Edit: I dare to tag @Mike_Ash :)

1 Like

swift-inspect is not usable on Linux - you will need to do a port of it. I haven't had time yet to do the port for Linux but should be relatively straightforward. All the infrastructure to support different platforms is now in place due to the Windows support being added. You should only need to implement the process inspection support (via ptrace).

Thanks for reply!

I have tried it on my macOS machine

swift-driver version: 1.45.2 Apple Swift version 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)
Target: x86_64-apple-macosx12.0

using following configuration

$ python3 build_script_helper.py --package-path . --build-path .build --toolchain /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain --configuration release

however, it failed too. It looks like there is no issue with SwiftRemoteMirror but a bunch of types are missing.

/Users/mikolasstuchlik/Developer/swift/tools/swift-inspect/Sources/swift-inspect/Operations/DumpConcurrency.swift:280:33: error: cannot find type 'swift_actor_info_t' in scope
  func decodeActorFlags(_ info: swift_actor_info_t) -> (
                                ^~~~~~~~~~~~~~~~~~
/Users/mikolasstuchlik/Developer/swift/tools/swift-inspect/Sources/swift-inspect/Operations/DumpConcurrency.swift:154:18: error: cannot find 'swift_reflection_metadataIsActor' in scope
    let result = swift_reflection_metadataIsActor(context, metadata) != 0
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/mikolasstuchlik/Developer/swift/tools/swift-inspect/Sources/swift-inspect/Operations/DumpConcurrency.swift:170:26: error: cannot find 'swift_reflection_asyncTaskInfo' in scope
    let reflectionInfo = swift_reflection_asyncTaskInfo(context, task)
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(redacted)
/Users/mikolasstuchlik/Developer/swift/tools/swift-inspect/Sources/swift-inspect/Operations/DumpConcurrency.swift:395:18: error: cannot find 'swift_reflection_actorInfo' in scope
      let info = swift_reflection_actorInfo(context, actor)
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~
(redacted)
/Users/mikolasstuchlik/Developer/swift/tools/swift-inspect/Sources/swift-inspect/Operations/DumpConcurrency.swift:425:17: error: cannot find 'swift_reflection_nextJob' in scope
          job = swift_reflection_nextJob(context, job);
                ^~~~~~~~~~~~~~~~~~~~~~~~
/Users/mikolasstuchlik/Developer/swift/tools/swift-inspect/Sources/swift-inspect/main.swift:47:36: error: protocol 'RemoteProcess' can only be used as a generic constraint because it has Self or associated type requirements
                      _ body: (any RemoteProcess) throws -> Void) throws {
                                   ^

I have removed error: type of expression is ambiguous without more context and error: generic parameter 'Element' could not be inferred from the output.

I have therefore removed the whole DumpConcurrency.swift file and modified the main.swift as follows

diff --git a/tools/swift-inspect/Sources/swift-inspect/main.swift b/tools/swift-inspect/Sources/swift-inspect/main.swift
index 649f31c7..5f0b6365 100644
--- a/tools/swift-inspect/Sources/swift-inspect/main.swift
+++ b/tools/swift-inspect/Sources/swift-inspect/main.swift
@@ -44,27 +44,16 @@ internal struct BacktraceOptions: ParsableArguments {
 
 
 internal func inspect(options: UniversalOptions,
-                      _ body: (any RemoteProcess) throws -> Void) throws {
+                      _ body: (DarwinRemoteProcess) throws -> Void) throws {
   guard let processId = process(matching: options.nameOrPid) else {
     print("No process found matching \(options.nameOrPid)")
     return
   }
-
-#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
   guard let process = DarwinRemoteProcess(processId: processId,
                                           forkCorpse: options.forkCorpse) else {
     print("Failed to create inspector for process id \(processId)")
     return
   }
-#elseif os(Windows)
-  guard let process = WindowsRemoteProcess(processId: processId) else {
-    print("Failed to create inspector for process id \(processId)")
-    return
-  }
-#else
-#error("Unsupported platform")
-#endif
-
   try body(process)
 }
 
@@ -79,7 +68,6 @@ internal struct SwiftInspect: ParsableCommand {
     DumpGenericMetadata.self,
     DumpCacheNodes.self,
     DumpArrays.self,
-    DumpConcurrency.self,
   ]
 #else
   static let subcommands: [ParsableCommand.Type] = [

basically, I have removed the now deleted DumpConcurrency option from the list of options and removed the any RemoteProcess syntax since it's not part of the Swift 5.6

The compilation then succeeded using the same command as above. I will try to run the tool in following days.

However, there is one thing that still bothers me @compnerd . Even if I implement something like LinuxRemoteProcess and use it in some sort of #elseif os(Linux) macro, I still won't be able to compile it on Linux, since the SwiftRemoteMirror module is not part of the toolchain, only the .so file. Is there any workaround :thinking:

1 Like

If the DSO is there - you should be able to work around it pretty easily. IIRC, the module in question is a C module, not a Swift module. That means that you should only need the corresponding headers and should be able to use that to construct the module.

1 Like

Thank you for your reply.

On my Linux system, I have downloaded THUNK DEVELOPMENT release of the Toolchain and used the header files located in the apple/swift repo I have previously checked-out to get the swift-inspect.

I only modified build_script_helper.py in following manner:

mikolas@mikolas-desktop:~/Developer/swift/tools/swift-inspect$ git diff build_script_helper.py 
diff --git a/tools/swift-inspect/build_script_helper.py b/tools/swift-inspect/build_script_helper.py
index d11e254d53c..5a9e68716b1 100755
--- a/tools/swift-inspect/build_script_helper.py
+++ b/tools/swift-inspect/build_script_helper.py
@@ -17,11 +17,11 @@ def perform_build(args, swiftbuild_path):
         "-Xswiftc",
         "-I",
         "-Xswiftc",
-        os.path.join(args.toolchain, 'usr', 'include', 'swift'),
+        "/home/mikolas/Developer/swift/include/swift",
         "-Xswiftc",
         "-L",
         "-Xswiftc",
-        os.path.join(args.toolchain, 'usr', 'lib', 'swift', 'macosx'),
+        os.path.join(args.toolchain, 'usr', 'lib', 'swift', 'linux'),
         "-Xswiftc",
         "-lswiftRemoteMirror"
     ]

The compilation fails successfully on the expected error: Unsupported platform directives.

I am interested in commiting LinuxRemoteProcess in the future, but it will take me probably few monts before I post anything. This would be the first time I will play around with ptrace and there is a lot to learn for me.

I have already found some materials, but if you (or anyone reading this :slight_smile:) happen to know about some good articles, documentation or blog posts that would be related to this topic, I would appreciate it.

I would like to post a little status update. (As always) I'm writin this post in part to attract any suggestions :) The progress on this was (and will be) slow, since I'm learning on the fly things, I haven't (ever) needed.

I have backed up my progress to a branch if anyone is interested...

I am able to peek and poke memory via ptrace. The biggest issue right now is with symbolicate, addressForSym and iterateHeap.

Iterating heap

I assume I would be able to differentiate between heap and other memory blocks by reading /proc/[pid]/maps and peeking the addresses by ptrace. But it seems to me, that those blocks are not individual malloc blocks but rather some blocks allocated by the stdlib used. I do not know yet how to get individual malloc blocks, maybe if I assume all the blocks are allocated by glibc...

Symbols

I haven't found any "linux api" nor "GNU extension" for accessing symbol tables of remote processes (and loaded objects). I have found an old blog post about using ptrace to symbolicate, but it would mean that I should work directly with ELF which is a going to require a lot of study at my side.

Anyway thats all I have for now. I know I'm slow, but this is the first time I have delved into a lower level Linux topic.

Hello @compnerd , I was working on the swift-inspect but the situation turned out to be "not so easy." It appears there is no easy way to get things like iterateHeap on Linux and the sourcecode just for the task of iterating heap has few thousands lines of Swift code.

I have published my work so far as a separate repo GitHub - mikolasstuchlik/memtool (I have created a tread about it Tool for analyzing heap of Linux Glibc (Swift) process in Swift)

The tool works as a proof of concept, but I was thinking about how should I approach future development. Is it even possible to merge into apple/swift dependency to a repository, that is not under direct control of apple? Or since the codebase is so large, can it even be "merged" with the existing swift-inspect?

Hello,

I've had the time recently to integrate memtool into a swift-inspect in my Swift fork but I have some issues. I am able to build it and run it - it attaches to the traced process and starts reading data.

Unfortunately, the data read from the process are probably incorrect since I'm not getting any results for either mode.

Example program
var root: [[Int]] = []

for _ in 0..<32 {
    var item: [Int] = []
    for i in 0..<32 {
        item.append(i)
    }

    root.append(item)
}


_ = readLine()
print(root)

Dump array

dump-arrays will iterate heap (and all of the allocated arrays in a simple program) but won't print anything at stdout (example 1) - I assume it somehow fails to recognize metadata.
One hint, that something does not work are those attempts to load made by swift-inspect:

Request [Load bytes]: 0x0000000000000006 size 8
Request [Load bytes]: 0x0000000000000006 size 56
Request [Load bytes]: 0xffffffffffffffff size 4

Dump raw metadata

dump-raw-metadata (and basically all other modes) will read _swift_debug_metadataAllocationIterationEnabled and then close, since it is set to 0. (example 2).
I have also tried to run the swift-inspect in lldb and convince it, _swift_debug_metadataAllocationIterationEnabled is set to 1 (example 3) but then the swift-inspect reads some more memory and then it ends without a hint.

That is as far as I've been able to get. I don't have any idea where to go from here. I have even tried to compile the toolchain on my own, but this failed too.

2 Likes

I have forgot to mention, that have solved issues above and a Pull Request is open. [tool] Implement initial support for `swift-inspect` under Linux 64-bit by mikolasstuchlik · Pull Request #63576 · apple/swift · GitHub

3 Likes