Debugging Vapor apps is harder than it should be?

I'm having a really frustrating time debugging my Vapor app in Xcode. If I turn on the "Swift error" breakpoint in Xcode, and then debug my app, I get constant breaks in NIO.System.wrapSyscall(…), which makes it almost impossible to meaningfully debug what is actually going wrong in my app. Here's a backtrace that occurs immediately after starting a test run:

Backtrace
* thread #5, name = 'NIO-ELT-#0', stop reason = breakpoint 3.1
    frame #0: 0x00007fff65bedf30 libswiftCore.dylib`swift_willThrow
  * frame #1: 0x0000000111f05a21 AppTests`wrapSyscall<T>(function="getpeername(socket:address:addressLength:)", body=0x0000000111f0b400 AppTests`reabstraction thunk helper from @callee_guaranteed () -> (@unowned Swift.Int32, @error @owned Swift.Error) to @escaping @callee_guaranteed () -> (@out Swift.Int32, @error @owned Swift.Error)partial apply forwarder with unmangled suffix ".19" at <compiler-generated>) at System.swift:145:13
    frame #2: 0x0000000111f0b33c AppTests`static Posix.getpeername(socket=9, address=0x00007000019a4ba0, addressLength=0x00007000019a4808, self=NIO.Posix) at System.swift:466:13
    frame #3: 0x0000000111de7f41 AppTests`closure #1 in BaseSocket.remoteAddress($0=9, $1=0x00007000019a4ba0, $2=0x00007000019a4808) at BaseSocket.swift:234:41
    frame #4: 0x0000000111de8583 AppTests`closure #1 in closure #1 in BaseSocket.get_addr(fd=9, body=0x0000000111de7f10 AppTests`closure #1 (Swift.Int32, Swift.UnsafeMutablePointer<__C.sockaddr>, Swift.UnsafeMutablePointer<Swift.UInt32>) throws -> () in NIO.BaseSocket.remoteAddress() throws -> NIO.SocketAddress at BaseSocket.swift:234, addressPtr=0x00007000019a4ba0, size=128) at BaseSocket.swift:245:21
    frame #5: 0x0000000111dec3bc AppTests`partial apply for closure #1 in closure #1 in BaseSocket.get_addr(_:) at <compiler-generated>:0
    frame #6: 0x0000000111de85cf AppTests`thunk for @callee_guaranteed (@unowned Int32) -> (@error @owned Error) at <compiler-generated>:0
    frame #7: 0x0000000111dec3e4 AppTests`thunk for @callee_guaranteed (@unowned Int32) -> (@error @owned Error)partial apply at <compiler-generated>:0
    frame #8: 0x0000000111de7c7f AppTests`BaseSocket.withUnsafeFileDescriptor<T>(body=0x0000000111dec3d0 AppTests`reabstraction thunk helper from @callee_guaranteed (@unowned Swift.Int32) -> (@error @owned Swift.Error) to @escaping @callee_guaranteed (@unowned Swift.Int32) -> (@out (), @error @owned Swift.Error)partial apply forwarder with unmangled suffix ".8" at <compiler-generated>, self=(descriptor = 9)) at BaseSocket.swift:218:20
    frame #9: 0x0000000111de84da AppTests`closure #1 in BaseSocket.get_addr(addressPtr=0x00007000019a4ba0, size=128, self=(descriptor = 9), body=0x0000000111de7f10 AppTests`closure #1 (Swift.Int32, Swift.UnsafeMutablePointer<__C.sockaddr>, Swift.UnsafeMutablePointer<Swift.UInt32>) throws -> () in NIO.BaseSocket.remoteAddress() throws -> NIO.SocketAddress at BaseSocket.swift:234) at BaseSocket.swift:244:17
    frame #10: 0x0000000111dec378 AppTests`partial apply for closure #1 in BaseSocket.get_addr(_:) at <compiler-generated>:0
    frame #11: 0x0000000111de862f AppTests`thunk for @callee_guaranteed (@unowned UnsafeMutablePointer<sockaddr>, @unowned Int) -> (@error @owned Error) at <compiler-generated>:0
    frame #12: 0x0000000111dec394 AppTests`partial apply for thunk for @callee_guaranteed (@unowned UnsafeMutablePointer<sockaddr>, @unowned Int) -> (@error @owned Error) at <compiler-generated>:0
    frame #13: 0x0000000111de4df5 AppTests`closure #1 in sockaddr_storage.withMutableSockAddr<R>(p=Swift.UnsafeMutableRawBufferPointer @ 0x00007000019a49a0, body=0x0000000111dec380 AppTests`partial apply forwarder for reabstraction thunk helper from @callee_guaranteed (@unowned Swift.UnsafeMutablePointer<__C.sockaddr>, @unowned Swift.Int) -> (@error @owned Swift.Error) to @escaping @callee_guaranteed (@unowned Swift.UnsafeMutablePointer<__C.sockaddr>, @unowned Swift.Int) -> (@out (), @error @owned Swift.Error) at <compiler-generated>) at BaseSocket.swift:137:17
    frame #14: 0x0000000111de4e7a AppTests`partial apply for closure #1 in sockaddr_storage.withMutableSockAddr<A>(_:) at <compiler-generated>:0
    frame #15: 0x00007fff6596af10 libswiftCore.dylib`merged Swift.withUnsafeMutableBytes<A, B>(of: inout A, _: (Swift.UnsafeMutableRawBufferPointer) throws -> B) throws -> B + 32
    frame #16: 0x00007fff6596aeac libswiftCore.dylib`Swift.withUnsafeMutableBytes<A, B>(of: inout A, _: (Swift.UnsafeMutableRawBufferPointer) throws -> B) throws -> B + 28
    frame #17: 0x0000000111de4c31 AppTests`sockaddr_storage.withMutableSockAddr<R>(body=0x0000000111dec380 AppTests`partial apply forwarder for reabstraction thunk helper from @callee_guaranteed (@unowned Swift.UnsafeMutablePointer<__C.sockaddr>, @unowned Swift.Int) -> (@error @owned Swift.Error) to @escaping @callee_guaranteed (@unowned Swift.UnsafeMutablePointer<__C.sockaddr>, @unowned Swift.Int) -> (@out (), @error @owned Swift.Error) at <compiler-generated>, self=Darwin.sockaddr_storage @ 0x00007000019a4ba0) at BaseSocket.swift:136:20
    frame #18: 0x0000000111de82da AppTests`BaseSocket.get_addr(body=0x0000000111de7f10 AppTests`closure #1 (Swift.Int32, Swift.UnsafeMutablePointer<__C.sockaddr>, Swift.UnsafeMutablePointer<Swift.UInt32>) throws -> () in NIO.BaseSocket.remoteAddress() throws -> NIO.SocketAddress at BaseSocket.swift:234, self=(descriptor = 9)) at BaseSocket.swift:241:18
    frame #19: 0x0000000111de7ec4 AppTests`BaseSocket.remoteAddress(self=(descriptor = 9)) at BaseSocket.swift:234:20
    frame #20: 0x0000000111df23b5 AppTests`BaseSocketChannel.init(socket=<no summary available>, parent=nil, eventLoop=0x0000000100bdff90, recvAllocator=NIO.AdaptiveRecvByteBufferAllocator @ 0x0000000100cb8bb0, self=<no summary available>) at BaseSocketChannel.swift:397:96
    frame #21: 0x0000000111ef065c AppTests`SocketChannel.init(eventLoop=0x0000000100bdff90, protocolFamily=30) at SocketChannel.swift:59:19
    frame #22: 0x0000000111ef021d AppTests`SocketChannel.__allocating_init(eventLoop:protocolFamily:) at SocketChannel.swift:0
    frame #23: 0x0000000111e0f6ea AppTests`ClientBootstrap.execute(eventLoop=(instance = 0x0000000100bdff90 -> 0x0000000112613c90 type metadata for NIO.SelectableEventLoop, witness_table_EventLoop = 0x00000001125d46d8 AppTests`protocol witness table for NIO.SelectableEventLoop : NIO.EventLoop in NIO), protocolFamily=30, body=0x0000000111e0f130 AppTests`closure #1 (NIO.Channel) -> NIO.EventLoopFuture<()> in closure #1 (NIO.EventLoop, Swift.Int32) -> NIO.EventLoopFuture<NIO.Channel> in NIO.ClientBootstrap.connect(host: Swift.String, port: Swift.Int) -> NIO.EventLoopFuture<NIO.Channel> at Bootstrap.swift:424, self=0x00000001065f9b50) at Bootstrap.swift:502:27
    frame #24: 0x0000000111e0f119 AppTests`closure #1 in ClientBootstrap.connect(eventLoop=(instance = 0x0000000100bdff90 -> 0x0000000112613c90 type metadata for NIO.SelectableEventLoop, witness_table_EventLoop = 0x00000001125d46d8 AppTests`protocol witness table for NIO.SelectableEventLoop : NIO.EventLoop in NIO), protocolFamily=30, self=0x00000001065f9b50) at Bootstrap.swift:424:25
    frame #25: 0x0000000111e9543d AppTests`HappyEyeballsConnector.connectToTarget(target=v6, self=0x00000001065f9f40) at HappyEyeballs.swift:529:29
    frame #26: 0x0000000111e94a69 AppTests`HappyEyeballsConnector.beginConnecting(self=0x00000001065f9f40) at HappyEyeballs.swift:459:9
    frame #27: 0x0000000111e93ab4 AppTests`HappyEyeballsConnector.processInput(input=resolverAAAACompleted, self=0x00000001065f9f40) at HappyEyeballs.swift:331:13
    frame #28: 0x0000000111e978cd AppTests`closure #3 in HappyEyeballsConnector.whenAAAALookupComplete(self=0x00000001065f9f40) at HappyEyeballs.swift:629:18
    frame #29: 0x0000000111e82737 AppTests`closure #1 in EventLoopFuture.whenComplete(callback=0x0000000111e99690 AppTests`partial apply forwarder for closure #3 () -> () in NIO.HappyEyeballsConnector.(whenAAAALookupComplete in _2EDDDA16464B5E6B93684A67F9A71C90)(future: NIO.EventLoopFuture<Swift.Array<NIO.SocketAddress>>) -> () at <compiler-generated>) at EventLoopFuture.swift:667:13
    frame #30: 0x0000000111e7fcb2 AppTests`EventLoopFuture._addCallback(callback=0x0000000111e849e0 AppTests`partial apply forwarder for closure #1 () -> NIO.(CallbackList in _1CAEE0056AB83634CE1A29DE57BC300C) in NIO.EventLoopFuture.whenComplete(() -> ()) -> () at <compiler-generated>, self=0x0000000100cb82c0) at EventLoopFuture.swift:597:16
    frame #31: 0x0000000111e7fea2 AppTests`EventLoopFuture._whenComplete(callback=0x0000000111e849e0 AppTests`partial apply forwarder for closure #1 () -> NIO.(CallbackList in _1CAEE0056AB83634CE1A29DE57BC300C) in NIO.EventLoopFuture.whenComplete(() -> ()) -> () at <compiler-generated>, self=0x0000000100cb82c0) at EventLoopFuture.swift:603:13
    frame #32: 0x0000000111e826e6 AppTests`EventLoopFuture.whenComplete(callback=0x0000000111e99690 AppTests`partial apply forwarder for closure #3 () -> () in NIO.HappyEyeballsConnector.(whenAAAALookupComplete in _2EDDDA16464B5E6B93684A67F9A71C90)(future: NIO.EventLoopFuture<Swift.Array<NIO.SocketAddress>>) -> () at <compiler-generated>, self=0x0000000100cb82c0) at EventLoopFuture.swift:666:9
    frame #33: 0x0000000111e97540 AppTests`HappyEyeballsConnector.whenAAAALookupComplete(future=0x00000001065f9ef0, self=0x00000001065f9f40) at HappyEyeballs.swift:621:11
    frame #34: 0x0000000111e943b1 AppTests`HappyEyeballsConnector.beginDNSResolution(self=0x00000001065f9f40) at HappyEyeballs.swift:431:9
    frame #35: 0x0000000111e939d0 AppTests`HappyEyeballsConnector.processInput(input=resolve, self=0x00000001065f9f40) at HappyEyeballs.swift:322:13
    frame #36: 0x0000000111e93857 AppTests`closure #1 in HappyEyeballsConnector.resolveAndConnect(self=0x00000001065f9f40) at HappyEyeballs.swift:308:18
    frame #37: 0x0000000111e71dec AppTests`thunk for @escaping @callee_guaranteed () -> () at <compiler-generated>:0
    frame #38: 0x0000000111e7a4e1 AppTests`partial apply for thunk for @escaping @callee_guaranteed () -> () at <compiler-generated>:0
    frame #39: 0x0000000111e71e0c AppTests`thunk for @escaping @callee_guaranteed () -> (@out ()) at <compiler-generated>:0
    frame #40: 0x0000000111e71e57 AppTests`closure #3 in SelectableEventLoop.run(task=0x0000000111e77880 AppTests`partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed () -> (@out ()) to @escaping @callee_guaranteed () -> () at <compiler-generated>) at EventLoop.swift:726:25
    frame #41: 0x0000000111df422f AppTests`thunk for @callee_guaranteed () -> (@error @owned Error) at <compiler-generated>:0
    frame #42: 0x0000000111e77844 AppTests`thunk for @callee_guaranteed () -> (@error @owned Error)partial apply at <compiler-generated>:0
    frame #43: 0x0000000111e6d4f2 AppTests`closure #1 in withAutoReleasePool<T>(execute=0x0000000111e77830 AppTests`reabstraction thunk helper from @callee_guaranteed () -> (@error @owned Swift.Error) to @escaping @callee_guaranteed () -> (@out (), @error @owned Swift.Error)partial apply forwarder with unmangled suffix ".20" at <compiler-generated>) at EventLoop.swift:451:13
    frame #44: 0x0000000111e7a50f AppTests`partial apply for closure #1 in withAutoReleasePool<A>(_:) at <compiler-generated>:0
    frame #45: 0x00007fff66118f0e libswiftObjectiveC.dylib`ObjectiveC.autoreleasepool<A>(invoking: () throws -> A) throws -> A + 46
    frame #46: 0x0000000111e6d499 AppTests`withAutoReleasePool<T>(execute=0x0000000111e77830 AppTests`reabstraction thunk helper from @callee_guaranteed () -> (@error @owned Swift.Error) to @escaping @callee_guaranteed () -> (@out (), @error @owned Swift.Error)partial apply forwarder with unmangled suffix ".20" at <compiler-generated>) at EventLoop.swift:450:16
    frame #47: 0x0000000111e70ddb AppTests`SelectableEventLoop.run(self=0x0000000100bdff90) at EventLoop.swift:725:21
    frame #48: 0x0000000111e7421c AppTests`closure #1 in static MultiThreadedEventLoopGroup.setupThreadAndEventLoop(t=(pthread = 0x00007000019a7000), initializer=0x0000000111e79f80 AppTests`partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@in_guaranteed NIO.Thread) -> (@out ()) to @escaping @callee_guaranteed (@guaranteed NIO.Thread) -> () at <compiler-generated>, self=0x0000000112613da0, lock=(mutex = 0x00000001065d6050), _loop=0x0000000100bdff90, loopUpAndRunningGroup=0x00000001065d6090) at EventLoop.swift:892:23
    frame #49: 0x0000000111e7a05a AppTests`partial apply for closure #1 in static MultiThreadedEventLoopGroup.setupThreadAndEventLoop(name:initializer:) at <compiler-generated>:0
    frame #50: 0x0000000111e747cf AppTests`thunk for @escaping @callee_guaranteed (@guaranteed Thread) -> () at <compiler-generated>:0
    frame #51: 0x0000000111f0d871 AppTests`partial apply for thunk for @escaping @callee_guaranteed (@guaranteed Thread) -> () at <compiler-generated>:0
    frame #52: 0x0000000111f0dc2b AppTests`closure #1 in static Thread.spawnAndRun(p=(_rawValue = 0x00000001065ef250)) at Thread.swift:104:13
    frame #53: 0x0000000111f0dca9 AppTests`@objc closure #1 in static Thread.spawnAndRun(name:body:) at <compiler-generated>:0
    frame #54: 0x00007fff66672d76 libsystem_pthread.dylib`_pthread_start + 125
    frame #55: 0x00007fff6666f5d3 libsystem_pthread.dylib`thread_start + 15

To be clear, this happens even when I run an otherwise fully-functional app with that breakpoint enabled. I brought this up in Discord late last week, and the response was essentially "don't use the Swift error breakpoint". That's not really realistic.

Are there any workarounds or fixes? Where should I be reporting this as a problem?

3 Likes

CC/ @tanner0101 @johannesweiss

While I appreciate the helping hand, I'm not sure it's necessary to tag specific people in, is it? I'm sure that by virtue of me posting in the Vapor forums, the relevant contributors will see.

1 Like

Hm, this is a multifaceted issue. Let's start at the bottom level.

wrapSyscall throws because that's how errors are reported in Swift. In general, if wrapSyscall has thrown there is no plan to recover from that error. In the case of your call stack we are attempting to work out if the socket provided to use already has a remote address, but as you're making an outbound connection it does not. We therefore suppress the error. You can expect to see at least one of these for all outbound connection attempts.

As you've correctly identified, this error is not fatal to your application because it is handled. So, why does this call throw?

Well, it throws to indicate an error. The user (in this case, a part of SwiftNIO) asked for the remote address for some reason. When an error occurs attempting to obtain it, we need to report that error. We can do it in three ways:

  1. Suppress the error, return nil. This is a debugging nightmare because you lose information at the low level of the stack that cannot be recovered, so let's rule this out immediately.
  2. Use the standard Swift error reporting mechanism, throw.
  3. Use Result.

We currently do (2), we've ruled out (1), so to give you the usage model you want we'd have to do (3). The question is: why should we do (3)? Swift has an error propagating model: it's try/throw/catch. As this error model exists today, an affirmative argument needs to be made to not use it. I think this brings us to your other comment:

This seems to be the real crux of the issue to me, so I think it's what we should address. I think we need to clarify a few things:

  1. What do you want to achieve with your use of the Swift error breakpoint?
  2. What tool do you need to achieve that goal?
  3. Why did you reach for the Swift error breakpoint?

Right now my reading of your implicit argument (and I'm definitely inferring a lot here, please do correct me if I've misunderstood) is that you are essentially saying: "Libraries such as SwiftNIO should only throw an error if it will never be handled by the library". That's already quite a strong position, but as you're debugging a Vapor application I think you actually need an even stronger one, which is: "Thrown errors should never be handled by libraries, but must always propagate to the application code".

I think that's a pretty strong assertion, and I don't think I agree with it. SwiftNIO is correctly throwing an error from one part of its code, and a different part has handled the error. This is not an unreasonable design pattern, it's not an abuse of the error handling mechanism, and I think I'd be surprised to find anyone in the Swift community who thought it was an unreasonable choice.

I'm open to being convinced here, but I definitely think the onus is on you to explain why this pattern is unreasonable, and what the pattern should be instead.

5 Likes

I agree with @lukasa: it is not reasonable to require libraries not to use Swift errors internally. Sometimes they just do not have any other choice, anyway.

However, I understand how painful debugging can become. I myself run in such trouble with Objective-C exceptions, because some Apple or third-party frameworks do use them internally, and this makes difficult to break on the exceptions I'm interested in. Debugging turns in a hide-and-seek breakpoint game that is not very funny.

Maybe a good solution for the OP would be to tell LLDB to break only if the error is thrown from a particular module? I don't know LLDB enough to tell if it is possible.

4 Likes

As far as I know, a breakpoint modifies in place the executable, at the location of the tracked function/method. Here we are talking about a single breakpoint, which is on the function that throws a Swift error. By default, it breaks on all Swift errors.

Now, LLDB breakpoints can have conditions (for some examples, see GDB to LLDB command map - 🐛 LLDB). In our specific case, the condition would test the module of the caller (unless we have a better idea eventually). But I'm having a hard time finding documentation about the syntax of such conditions, and the kind of stuff they can test. They generally look like expressions that involve local variables, though: nothing "meta" like the module.

May I ask @eskimo for a hint?

1 Like

@lukasa thanks for the detailed explanation of why wrapSyscall is throwing. Please don't misinterpret what I've written: At no point did I say that libraries shouldn't throw errors - as you state, it's a valid and accepted pattern of use, and one I use myself.

To be clear: I don't know what the reasonable answer is here. I am not placing the blame directly with Vapor, or with SwiftNIO - in the fullness of time I'm pretty sure this problem could/should be solved by changes to Xcode/LLDB. That's definitely not a realistic thing to pin my hopes on, though.

I really don't want or need to convince anyone of anything. I've started a forum thread talking about difficulties I'm facing, and you are asking me to justify my experience and provide an alternative design for a library I've only just started interacting with? You may not have intended it to be, but that's awfully hostile. I am looking for suggestions and help.

To answer the questions you posed:

  1. What do you want to achieve with your use of the Swift error breakpoint?

I'm trying to catch and debug errors being thrown in my app that are bubbling up from third party dependencies (often due wholly to misuse on my part, which is easy enough to see when I can catch and inspect the error that's being thrown).

  1. What tool do you need to achieve that goal?

I would have said breaking on Swift errors would give me a reasonable spot to start debugging issues as they occur in my real-time interactions with the server, however the signal-to-noise ratio due to SwiftNIO's use of errors makes that untenable. Stepping through the errors generated by SwiftNIO for every request results in my request timing out, so the original error I was chasing never gets a chance to eventuate.

  1. Why did you reach for the Swift error breakpoint?

Two reasons:

  1. It's what I'm familiar with from my experience developing desktop Cocoa apps;
  2. The errors that I'm trying to intercept are often thrown from within third-party dependencies, and initially, I'm unsure of where they're bubbling up from. Using a wide net to catch the error initially gives me
    a good idea of where/what is causing the problem.

I think it might be helpful for you to ignore all errors that are thrown from SwiftNIO. A pretty crude way (sorry, not really an lldb scripting expert) could be

break comm add --script-type python -o 'import lldb; import os; lldb.frame.GetThread().GetProcess().Continue() if os.path.basename(lldb.frame.GetThread().GetFrameAtIndex(1).GetModule().file.__str__()).startswith("NIO") else print("stopping")' 1

(Please note the 1 at the very end which is the break point number of the Swift error breakpoint and you might need to change that.)

The script basically checks if the module name starts with NIO and if yes, just continues. That should leave you with the more high-level errors thrown from elsewhere. Excluding further modules should also be rather straightforward.

4 Likes

I apologise unreservedly: I had no intention of being hostile. I didn't want you to justify your experience, only to elaborate on what you perceived the problem to be. I'm very sorry for failing to communicate that clearly.

This is a useful piece of feedback I think, as it suggests the approach proposed by other posters here is the right one. You are using a very broad brush to locate errors. This is intentional on your part: you want to see all errors that might reach you, to ensure you see the ones that actually do reach you. This suggests you may want to be using filtering on the breakpoint to avoid those errors that you've already checked for.

However, this may be insufficient. In particular, breakpoint scripts do still require stopping the running process to run, so they can be quite slow. If you're hitting a large enough volume of them you may find that even that is insufficient to prevent a timeout.

Can I also propose another alternative? If the errors are bubbling up into your code, you can place a breakpoint at the point where they are thrown to determine their types. At that stage, you can then break on the initializer for the errors to try to locate where they are being constructed, which may give you less volume.

2 Likes

Thanks for the apology - I appreciated you taking the time to walk through the potential paths, and why it was happening.

Can I also propose another alternative? If the errors are bubbling up into your code, you can place a breakpoint at the point where they are thrown to determine their types.

A good night's sleep has given me another idea for the moment - I'm going to write a small piece of middleware for Vapor that catches errors, and then passes on the original request to the rest of the middleware (thereby not interfering with the normal function of my app). That will give me a single spot I can set a breakpoint and capture any errors that bubble up.

There's a lot to be said for sleeping! :smile:

They generally look like expressions that involve local variables,
though: nothing "meta" like the module.

Correct, but that shouldn’t stop you (-: There’s two ways you might approach this:

  • Set a breakpoint on swift_willThrow and look at the return address to see where it’s coming from. The return address is architecture specific, but in practice there’s very few architectures you have to cope with: On Arm the return address is in $lr and on 64-bit Intel it’s on top of the stack, that is, (*(void**)$rsp).

  • Another interesting tidbit is that the actual thrown exception is passed as the second argument to swift_willThrow, meaning you can read it from $arg2. wrapSyscall always throws an IOError, so if you could find a reliable way to identify that then you could filter out those throws.

    I played around with this a bit but wasn’t able to find a perfect solution. If IOError were a class, you could detect it with an expression like this:

      !(BOOL)[[(id)$arg2 description] isEqual:@"xxx.IOError"]
    

    However, I suspect IOError is a struct, and that makes things trickier. Specifically, the breakpoint expression is, by default, evaluated as Objective-C, so you can’t do something simple like $arg2 is IOError. You can change the language (using either the -L argument to breakpoint set or the target.language setting), but changing it to Swift seems to cut you off from access to $arg2.

    I’m sure there’s some clever way to get this working but I’ve run out of time to look at it further today.

Regardless, it’s clear that this is a target rich environment for enhancement request. It’d be great if LLDB, and even Xcode’s breakpoint UI on top of that, had a way to filter out unwanted error / exception breakpoints.

Please post the numbers of any bugs you file, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

You can just use LLDB's powers to do this for you. As I outlined above, the following LLDB python will give you the actual module that threw the error

lldb.frame.GetThread().GetFrameAtIndex(1).GetModule()

apart from the module there's also plenty of other information available that you can examine for example using

script lldb.frame.GetThread().GetFrameAtIndex(1).GetModule().__dir__()

whilst you're in the breakpoint. (The script in lldb just runs python scripts and later on, you can use it in a breakpoint command and automatically continue if not interested.)

1 Like

Oh, that's tidy! Thanks for the LLDB info @johannesweiss!

@eskimo I'll make some time this week to file all of the feedback I've promised you over the last few weeks - I swear I'm good for it :sweat_smile:

I've filed FB7247232 asking for breakpoint module filtering. Thanks, Quinn!

2 Likes