Skip to content

Commit 3fd1ee2

Browse files
gh-worker-dd-mergequeue-cf854d[bot]maxepdd-mergequeue[bot]ncreatedsimaoseica-dd
authored
Merge pull request #2649 from DataDog/hotfix/3.5.1
Merge `hotfix/3.5.1` to `develop` Co-authored-by: maxep <maxime.epain@datadoghq.com> Co-authored-by: gh-worker-dd-mergequeue-cf854d[bot] <244855004+gh-worker-dd-mergequeue-cf854d[bot]@users.noreply.github.com> Co-authored-by: dd-mergequeue[bot] <121105855+dd-mergequeue[bot]@users.noreply.github.com> Co-authored-by: ncreated <maciek.grzybowski@datadoghq.com> Co-authored-by: simaoseica-dd <simao.seica@datadoghq.com> Co-authored-by: mariedm <29802155+mariedm@users.noreply.github.com>
2 parents d8db9d9 + 5bc278e commit 3fd1ee2

17 files changed

+129
-36
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
- [IMPROVEMENT] Add `DDLogEventUserInfo.anonymousId` property in ObjC API. See [#2640][]
44
- [FEATURE] Support manually keeping or dropping a trace. See [#2639][]
55

6+
# 3.5.1 / 23-01-2025
7+
8+
- [FIX] Fix crash in App Hangs backtrace generation. See [#2647][]
9+
610
# 3.5.0 / 12-01-2025
711

812
- [FEATURE] Report time to initial display (TTID). See [#2517][] [#2464][]
@@ -1022,6 +1026,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
10221026
[#2614]: https://github.com/DataDog/dd-sdk-ios/pull/2614
10231027
[#2631]: https://github.com/DataDog/dd-sdk-ios/pull/2631
10241028
[#2633]: https://github.com/DataDog/dd-sdk-ios/pull/2633
1029+
[#2647]: https://github.com/DataDog/dd-sdk-ios/pull/2647
10251030
[#2640]: https://github.com/DataDog/dd-sdk-ios/pull/2640
10261031
[#2639]: https://github.com/DataDog/dd-sdk-ios/pull/2639
10271032

DatadogCore.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogCore"
3-
s.version = "3.5.0"
3+
s.version = "3.5.1"
44
s.summary = "Official Datadog Swift SDK for iOS."
55

66
s.homepage = "https://www.datadoghq.com"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// GENERATED FILE: Do not edit directly
22

3-
internal let __sdkVersion = "3.5.0"
3+
internal let __sdkVersion = "3.5.1"

DatadogCrashReporting.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogCrashReporting"
3-
s.version = "3.5.0"
3+
s.version = "3.5.1"
44
s.summary = "Official Datadog Crash Reporting SDK for iOS."
55

66
s.homepage = "https://www.datadoghq.com"

DatadogCrashReporting/Sources/KSCrashIntegration/DatadogCrashReportFilter.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,20 @@ internal import KSCrashRecording
3737
/// If a report cannot be converted (invalid format, missing required fields),
3838
/// the original report is passed through and an error is provided to the completion handler.
3939
internal final class DatadogCrashReportFilter: NSObject, CrashReportFilter {
40-
// Parse timestamp with fractional seconds support
41-
// KSCrash timestamps use ISO8601 format with microsecond precision (e.g., "2025-10-22T14:14:12.007336Z")
40+
/// Parse timestamp with fractional seconds support
41+
/// KSCrash timestamps use ISO8601 format with microsecond precision (e.g., "2025-10-22T14:14:12.007336Z")
4242
let dateFormatter = {
4343
let formatter = ISO8601DateFormatter()
4444
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
4545
return formatter
4646
}()
4747

48+
let telemetry: Telemetry
49+
50+
init(telemetry: Telemetry = NOPTelemetry()) {
51+
self.telemetry = telemetry
52+
}
53+
4854
/// Filters and converts crash reports to Datadog's format.
4955
///
5056
/// This method processes each report by converting it from KSCrash's format
@@ -152,8 +158,16 @@ internal final class DatadogCrashReportFilter: NSObject, CrashReportFilter {
152158
return String(format: "%-4ld ??? 0x%016llx 0x0 + 0", index, instructionAddr)
153159
}
154160

161+
let offset: Int64
162+
if instructionAddr >= objectAddr {
163+
offset = instructionAddr - objectAddr
164+
} else {
165+
offset = 0
166+
telemetry.error("Invalid image load address, symbolication will fail")
167+
}
168+
155169
// Format: frame_index (4 chars left-aligned) + library_name (35 chars left-aligned) + instruction_addr + image_base_addr + offset
156-
return String(format: "%-4ld %-35@ 0x%016llx 0x%016llx + %lld", index, objectName, instructionAddr, objectAddr, instructionAddr - objectAddr)
170+
return String(format: "%-4ld %-35@ 0x%016llx 0x%016llx + %lld", index, objectName, instructionAddr, objectAddr, offset)
157171
}
158172

159173
let index: Int64 = try thread.value(forKey: .index)

DatadogCrashReporting/Sources/KSCrashIntegration/KSCrashBacktrace.swift

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal struct KSCrashBacktrace: BacktraceReporting {
3535
func generateBacktrace(threadID: ThreadID) throws -> BacktraceReport? {
3636
// Convert Mach thread_t to pthread_t
3737
guard let pthread = pthread_from_mach_thread_np(threadID) else {
38-
telemetry.error("[KSCrashBacktrace] Failed to get pthread for thread with ID: \(threadID)")
38+
telemetry.error("Failed to get pthread for thread with ID: \(threadID)")
3939
return nil
4040
}
4141

@@ -66,6 +66,14 @@ internal struct KSCrashBacktrace: BacktraceReporting {
6666
let loadAddress = binaryImage.address
6767
let uuid = UUID(uuid: imageUUID.withMemoryRebound(to: uuid_t.self, capacity: 1) { $0.pointee })
6868

69+
let offset: UInt64
70+
if address >= loadAddress {
71+
offset = UInt64(address) - loadAddress
72+
} else {
73+
offset = 0
74+
telemetry.error("Invalid image load address, symbolication will fail")
75+
}
76+
6977
// Get architecture from binary image's CPU type
7078
let architecture = String(cString: kscpu_archForCPU(
7179
cpu_type_t(binaryImage.cpuType),
@@ -84,7 +92,7 @@ internal struct KSCrashBacktrace: BacktraceReporting {
8492
}
8593

8694
// Format: frame_index (4 chars left-aligned) + library_name (35 chars left-aligned) + addresses + offset
87-
return String(format: "%-4ld %-35@ 0x%016llx 0x%016llx + %lld", index, libraryName, address, loadAddress, UInt64(address) - loadAddress)
95+
return String(format: "%-4ld %-35@ 0x%016llx 0x%016llx + %lld", index, libraryName, address, loadAddress, offset)
8896
}
8997
.joined(separator: "\n")
9098

@@ -108,7 +116,7 @@ internal struct KSCrashBacktrace: BacktraceReporting {
108116
private func getThreadName(pthread: pthread_t) -> String? {
109117
var buffer = [CChar](repeating: 0, count: 256)
110118
guard pthread_getname_np(pthread, &buffer, buffer.count) == KERN_SUCCESS, buffer[0] != 0 else {
111-
telemetry.error("[KSCrashBacktrace] Failed to get pthread name")
119+
telemetry.error("Failed to get pthread name")
112120
return nil // fails or empty
113121
}
114122
return String(cString: buffer)
@@ -124,16 +132,19 @@ internal struct KSCrashBacktrace: BacktraceReporting {
124132
/// This allows us to determine the correct architecture (arm64, arm64e, etc.) for each
125133
/// binary image, which is critical for accurate stacktrace symbolication.
126134
private func symbolicate(address: uintptr_t) -> KSBinaryImage? {
127-
// initalize the binary image cache.
128-
// this has an atomic check so isn't expensive
129-
// except for the first call.
130-
ksbic_init()
131-
132135
let untaggedAddress = kssymbolicator_callInstructionAddress(address)
133-
136+
let instructionPointer = UnsafeRawPointer(bitPattern: untaggedAddress)
137+
138+
// Use standard dladdr() instead of ksdl_dladdr() to work around a bug where ksdl_dladdr()
139+
// can return the wrong image when looking up addresses in the presence of dynamic
140+
// frameworks. The bug causes unsigned arithmetic underflow when calculating the
141+
// instruction offset.
142+
//
143+
// This was fixed in KSCrash and is currently pending release.
144+
// See: https://github.com/DataDog/dd-sdk-ios/issues/2645
134145
var info = Dl_info()
135146
guard
136-
ksdl_dladdr(untaggedAddress, &info),
147+
dladdr(instructionPointer, &info) != 0,
137148
let baseAddress = info.dli_fbase,
138149
let fileName = info.dli_fname
139150
else {

DatadogCrashReporting/Sources/KSCrashIntegration/KSCrashPlugin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ internal class KSCrashPlugin: NSObject, CrashReportingPlugin {
3434
DatadogTypeSafeFilter(),
3535
DatadogMinifyFilter(),
3636
DatadogDiagnosticFilter(),
37-
DatadogCrashReportFilter()
37+
DatadogCrashReportFilter(telemetry: telemetry)
3838
]
3939
)
4040
} catch KSCrashInstallError.alreadyInstalled {

DatadogCrashReporting/Tests/KSCrashIntegration/DatadogCrashReportFilterTests.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,4 +476,67 @@ class DatadogCrashReportFilterTests: XCTestCase {
476476
XCTAssertEqual(ddReport1.meta.incidentIdentifier, "1")
477477
XCTAssertEqual(ddReport2.meta.incidentIdentifier, "2")
478478
}
479+
480+
func testFilterReports_HandlesInvalidOffsetCalculation() throws {
481+
// Given - instruction_addr less than object_addr (should not happen in practice)
482+
let json = """
483+
{
484+
"report": {
485+
"timestamp": "2025-10-22T14:14:12Z",
486+
"id": "incident-invalid-offset"
487+
},
488+
"system": {
489+
"cpu_arch": "arm64"
490+
},
491+
"crash": {
492+
"error": {
493+
"signal": {
494+
"name": "SIGSEGV"
495+
}
496+
},
497+
"threads": [
498+
{
499+
"index": 0,
500+
"crashed": true,
501+
"backtrace": {
502+
"contents": [
503+
{
504+
"instruction_addr": 1000,
505+
"object_addr": 5000,
506+
"object_name": "MyApp"
507+
}
508+
]
509+
}
510+
}
511+
]
512+
},
513+
"binary_images": []
514+
}
515+
""".data(using: .utf8)!
516+
517+
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: json) as? [String: Any])
518+
let report = AnyCrashReport(CrashFieldDictionary(from: dict))
519+
let filter = DatadogCrashReportFilter()
520+
var capturedReports: [CrashReport]?
521+
522+
// When
523+
filter.filterReports([report]) { reports, error in
524+
XCTAssertNil(error)
525+
capturedReports = reports
526+
}
527+
528+
// Then
529+
let ddReport = try XCTUnwrap(capturedReports?.first?.untypedValue as? DDCrashReport)
530+
531+
// Verify stack format using regex parser
532+
let stackLines = ddReport.stack.split(separator: "\n")
533+
XCTAssertEqual(stackLines.count, 1, "Should have 1 stack frame")
534+
535+
let parsed = try parseStackFrame(String(stackLines[0]))
536+
XCTAssertEqual(parsed.index, 0)
537+
XCTAssertEqual(parsed.libraryName, "MyApp")
538+
XCTAssertEqual(parsed.instructionAddr, "0x00000000000003e8")
539+
XCTAssertEqual(parsed.loadAddr, "0x0000000000001388")
540+
XCTAssertEqual(parsed.offset, 0, "Offset should be 0 when instruction_addr < object_addr (prevents underflow)")
541+
}
479542
}

DatadogFlags.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogFlags"
3-
s.version = "3.5.0"
3+
s.version = "3.5.1"
44
s.summary = "Official Datadog Feature Flags module of the Swift SDK."
55

66
s.homepage = "https://www.datadoghq.com"

DatadogInternal.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "DatadogInternal"
3-
s.version = "3.5.0"
3+
s.version = "3.5.1"
44
s.summary = "Datadog Internal Package. This module is not for public use."
55

66
s.homepage = "https://www.datadoghq.com"

0 commit comments

Comments
 (0)