Skip to content

Commit d1fb6e2

Browse files
robert-smartbearRobert
authored andcommitted
Async safe number to string conversion (#10)
* Async-safe number-to-string conversion * Changes requested in code review --------- Co-authored-by: Robert <[email protected]>
1 parent 7d9e784 commit d1fb6e2

File tree

8 files changed

+305
-10
lines changed

8 files changed

+305
-10
lines changed

Samples/Common/Sources/IntegrationTestsHelper/InstallConfig.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public struct InstallConfig: Codable {
3131
public var installPath: String
3232
public var isCxaThrowEnabled: Bool?
3333
public var isSigTermMonitoringEnabled: Bool?
34+
public var isMemoryIntrospectionEnabled: Bool?
3435

3536
public init(installPath: String) {
3637
self.installPath = installPath
@@ -47,6 +48,9 @@ extension InstallConfig {
4748
if let isSigTermMonitoringEnabled {
4849
config.enableSigTermMonitoring = isSigTermMonitoringEnabled
4950
}
51+
if let isMemoryIntrospectionEnabled {
52+
config.enableMemoryIntrospection = isMemoryIntrospectionEnabled
53+
}
5054
try KSCrash.shared.install(with: config)
5155
}
5256
}

Samples/Tests/Core/PartialCrashReport.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,16 @@ struct PartialCrashReport: Decodable {
8585

8686
var contents: [Frame]
8787
}
88+
struct Address: Decodable {
89+
var address: UInt64?
90+
var type: String?
91+
}
8892

8993
var index: Int
9094
var state: String?
9195
var crashed: Bool
9296
var backtrace: Backtrace
97+
var notable_addresses: [String: Address]?
9398
}
9499

95100
var error: Error?

Samples/Tests/IntegrationTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ final class CppTests: IntegrationTestBase {
113113
print(appleReport)
114114
XCTAssertTrue(appleReport.contains("SIGTERM"))
115115
}
116+
117+
func testTerminationWithMemoryIntrospection() throws {
118+
try launchAndInstall { config in
119+
config.isSigTermMonitoringEnabled = true
120+
config.isMemoryIntrospectionEnabled = true
121+
}
122+
try terminate()
123+
124+
let rawReport = try readPartialCrashReport()
125+
try rawReport.validate()
126+
XCTAssertNotNil(rawReport.crash?.threads?.first?.notable_addresses)
127+
XCTAssertTrue(
128+
rawReport.crash?.threads?.first?.notable_addresses?.keys.contains { $0.hasPrefix("stack@0x") } ?? false)
129+
}
116130
}
117131

118132
#endif

Sources/KSCrashCore/include/KSCrashNamespace.h

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,7 @@
177177
#define isSwiftSymbol KSCRASH_NS(isSwiftSymbol)
178178
#define ksapp_transitionStateIsUserPerceptible KSCRASH_NS(ksapp_transitionStateIsUserPerceptible)
179179
#define ksapp_transitionStateToString KSCRASH_NS(ksapp_transitionStateToString)
180-
#define ksbic_imageCount KSCRASH_NS(ksbic_imageCount)
181-
#define ksbic_imageHeader KSCRASH_NS(ksbic_imageHeader)
182-
#define ksbic_imageName KSCRASH_NS(ksbic_imageName)
183-
#define ksbic_imageVMAddrSlide KSCRASH_NS(ksbic_imageVMAddrSlide)
180+
#define ksbic_getImages KSCRASH_NS(ksbic_getImages)
184181
#define ksbic_init KSCRASH_NS(ksbic_init)
185182
#define ksbic_resetCache KSCRASH_NS(ksbic_resetCache)
186183
#define ksbt_captureBacktrace KSCRASH_NS(ksbt_captureBacktrace)
@@ -272,12 +269,8 @@
272269
#define ksdate_utcStringFromMicroseconds KSCRASH_NS(ksdate_utcStringFromMicroseconds)
273270
#define ksdate_utcStringFromTimestamp KSCRASH_NS(ksdate_utcStringFromTimestamp)
274271
#define ksdebug_isBeingTraced KSCRASH_NS(ksdebug_isBeingTraced)
272+
#define ksdl_binaryImageForHeader KSCRASH_NS(ksdl_binaryImageForHeader)
275273
#define ksdl_dladdr KSCRASH_NS(ksdl_dladdr)
276-
#define ksdl_getBinaryImage KSCRASH_NS(ksdl_getBinaryImage)
277-
#define ksdl_getBinaryImageForHeader KSCRASH_NS(ksdl_getBinaryImageForHeader)
278-
#define ksdl_imageCount KSCRASH_NS(ksdl_imageCount)
279-
#define ksdl_imageNamed KSCRASH_NS(ksdl_imageNamed)
280-
#define ksdl_imageUUID KSCRASH_NS(ksdl_imageUUID)
281274
#define ksdm_demangleCPP KSCRASH_NS(ksdm_demangleCPP)
282275
#define ksdm_demangleSwift KSCRASH_NS(ksdm_demangleSwift)
283276
#define ksfu_closeBufferedReader KSCRASH_NS(ksfu_closeBufferedReader)
@@ -396,6 +389,8 @@
396389
#define kssc_initWithBacktrace KSCRASH_NS(kssc_initWithBacktrace)
397390
#define kssc_initWithMachineContext KSCRASH_NS(kssc_initWithMachineContext)
398391
#define kssc_resetCursor KSCRASH_NS(kssc_resetCursor)
392+
#define kssc_uint64_to_hex KSCRASH_NS(kssc_uint64_to_hex)
393+
#define kssc_uuid_to_string KSCRASH_NS(kssc_uuid_to_string)
399394
#define kssignal_fatalSignals KSCRASH_NS(kssignal_fatalSignals)
400395
#define kssignal_numFatalSignals KSCRASH_NS(kssignal_numFatalSignals)
401396
#define kssignal_signalCodeName KSCRASH_NS(kssignal_signalCodeName)

Sources/KSCrashRecording/KSCrashReportC.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include "KSStackCursor_Backtrace.h"
5454
#include "KSStackCursor_MachineContext.h"
5555
#include "KSString.h"
56+
#include "KSStringConversion.h"
5657
#include "KSSystemCapabilities.h"
5758
#include "KSThread.h"
5859
#include "KSThreadCache.h"
@@ -904,7 +905,9 @@ static void writeNotableStackContents(const KSCrashReportWriter *const writer,
904905
char nameBuffer[40];
905906
for (uintptr_t address = lowAddress; address < highAddress; address += sizeof(address)) {
906907
if (ksmem_copySafely((void *)address, &contentsAsPointer, sizeof(contentsAsPointer))) {
907-
sprintf(nameBuffer, "stack@%p", (void *)address);
908+
memcpy(nameBuffer, "stack@0x", 8);
909+
char *addressStart = nameBuffer + 8;
910+
kssc_uint64_to_hex((uintptr_t)address, addressStart, 1, false);
908911
writeMemoryContentsIfNotable(writer, nameBuffer, contentsAsPointer);
909912
}
910913
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// KSStringConversion.c
3+
//
4+
// Created by Robert B on 2025-04-23.
5+
//
6+
// Copyright (c) 2025 Karl Stenerud. All rights reserved.
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall remain in place
16+
// in this source code.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
// THE SOFTWARE.
25+
//
26+
27+
#include "KSStringConversion.h"
28+
29+
#include <math.h>
30+
#include <memory.h>
31+
#include <stdio.h>
32+
#include <uuid/uuid.h>
33+
34+
// Max uint64 is 18446744073709551615
35+
#define MAX_UINT64_DIGITS 20
36+
37+
static char g_hexNybbles[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
38+
39+
static char g_hexNybblesUppercase[] = {
40+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
41+
};
42+
43+
static int uuidSegmentLengths[5] = { 4, 2, 2, 2, 6 };
44+
45+
size_t kssc_uint64_to_hex(uint64_t value, char *dst, int min_digits, bool uppercase)
46+
{
47+
if (min_digits < 1) {
48+
min_digits = 1;
49+
} else if (min_digits > 16) {
50+
min_digits = 16;
51+
}
52+
53+
char buff[MAX_UINT64_DIGITS + 1];
54+
buff[sizeof(buff) - 1] = 0;
55+
size_t index = sizeof(buff) - 2;
56+
for (int digitCount = 1;; digitCount++) {
57+
buff[index] = uppercase ? g_hexNybblesUppercase[(value & 15)] : g_hexNybbles[(value & 15)];
58+
value >>= 4;
59+
if (value == 0 && digitCount >= min_digits) {
60+
break;
61+
}
62+
index--;
63+
}
64+
65+
size_t length = sizeof(buff) - index;
66+
memcpy(dst, buff + index, length);
67+
return length - 1;
68+
}
69+
70+
void kssc_uuid_to_string(uuid_t uuid, char *dst)
71+
{
72+
char *currentDst = dst;
73+
int valueIndex = 0;
74+
for (int segmentIndex = 0; segmentIndex < 5; segmentIndex++) {
75+
int segmentLength = uuidSegmentLengths[segmentIndex];
76+
for (int i = 0; i < segmentLength; i++) {
77+
kssc_uint64_to_hex(uuid[valueIndex++], currentDst, 2, true);
78+
currentDst += 2;
79+
}
80+
if (segmentIndex != 4) {
81+
memcpy(currentDst++, "-", 1);
82+
}
83+
}
84+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// KSStringConversion.h
3+
//
4+
// Created by Robert B on 2025-04-23.
5+
//
6+
// Copyright (c) 2025 Karl Stenerud. All rights reserved.
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall remain in place
16+
// in this source code.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
// THE SOFTWARE.
25+
//
26+
27+
#ifndef HDR_KSStringConversion_h
28+
#define HDR_KSStringConversion_h
29+
30+
#include <stdbool.h>
31+
#include <stddef.h>
32+
#include <stdint.h>
33+
#include <uuid/uuid.h>
34+
35+
#ifdef __cplusplus
36+
extern "C" {
37+
#endif
38+
39+
/**
40+
* Convert an unsigned integer to a hex string.
41+
* This will write a maximum of 17 characters (including the NUL) to dst.
42+
*
43+
* If min_digits is greater than 1, it will prepad with zeroes to reach this number of digits
44+
* (up to a maximum of 16 digits).
45+
*
46+
* Returns the length of the string written to dst (not including the NUL).
47+
*/
48+
size_t kssc_uint64_to_hex(uint64_t value, char *dst, int min_digits, bool uppercase);
49+
50+
/**
51+
* Convert an uuid_t to an uuid string.
52+
* This will write 37 characters (including the NUL) to dst.
53+
*/
54+
void kssc_uuid_to_string(uuid_t value, char *dst);
55+
56+
#ifdef __cplusplus
57+
}
58+
#endif
59+
60+
#endif // HDR_KSStringConversion_h
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
// KSStringConversion_Tests.m
3+
//
4+
// Created by Robert B on 2025-04-23.
5+
//
6+
// Copyright (c) 2012 Karl Stenerud. All rights reserved.
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall remain in place
16+
// in this source code.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
// THE SOFTWARE.
25+
//
26+
27+
#import <XCTest/XCTest.h>
28+
29+
#import "KSStringConversion.h"
30+
31+
@interface KSStringConversion_Tests : XCTestCase
32+
@end
33+
34+
@implementation KSStringConversion_Tests
35+
36+
- (void)testConvertUint64ZeroToString
37+
{
38+
uint64_t value = 0;
39+
char result[17];
40+
41+
size_t size = kssc_uint64_to_hex(value, result, 0, false);
42+
43+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
44+
XCTAssertEqual(size, 1);
45+
XCTAssertTrue([resultString isEqualToString:@"0"]);
46+
}
47+
48+
- (void)testConvertUint64ZeroWithMinDigitsToString
49+
{
50+
uint64_t value = 0;
51+
char result[17];
52+
53+
size_t size = kssc_uint64_to_hex(value, result, 10, false);
54+
55+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
56+
XCTAssertEqual(size, 10);
57+
XCTAssertTrue([resultString isEqualToString:@"0000000000"]);
58+
}
59+
60+
- (void)testConvertUint64MaxToString
61+
{
62+
uint64_t value = UINT64_MAX;
63+
char result[17];
64+
65+
size_t size = kssc_uint64_to_hex(value, result, 0, false);
66+
67+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
68+
XCTAssertEqual(size, 16);
69+
XCTAssertTrue([resultString isEqualToString:@"ffffffffffffffff"]);
70+
}
71+
72+
- (void)testConvertUint64MaxToUppercaseString
73+
{
74+
uint64_t value = UINT64_MAX;
75+
char result[17];
76+
77+
size_t size = kssc_uint64_to_hex(value, result, 0, true);
78+
79+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
80+
XCTAssertEqual(size, 16);
81+
XCTAssertTrue([resultString isEqualToString:@"FFFFFFFFFFFFFFFF"]);
82+
}
83+
84+
- (void)testConvertUint64ToString
85+
{
86+
uint64_t value = 0x2fe5c7f8;
87+
char result[17];
88+
89+
size_t size = kssc_uint64_to_hex(value, result, 0, false);
90+
91+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
92+
XCTAssertEqual(size, 8);
93+
XCTAssertTrue([resultString isEqualToString:@"2fe5c7f8"]);
94+
}
95+
96+
- (void)testConvertUint64ToUppercaseString
97+
{
98+
uint64_t value = 0x2fe5c7f8;
99+
char result[17];
100+
101+
size_t size = kssc_uint64_to_hex(value, result, 0, true);
102+
103+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
104+
XCTAssertEqual(size, 8);
105+
XCTAssertTrue([resultString isEqualToString:@"2FE5C7F8"]);
106+
}
107+
108+
- (void)testConvertAllZerosUUIDToString
109+
{
110+
uuid_t uuid = { 0 };
111+
char result[37];
112+
113+
kssc_uuid_to_string(uuid, result);
114+
115+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
116+
XCTAssertTrue([resultString isEqualToString:@"00000000-0000-0000-0000-000000000000"]);
117+
}
118+
119+
- (void)testConvertUUIDToString
120+
{
121+
uuid_t uuid = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
122+
char result[37];
123+
124+
kssc_uuid_to_string(uuid, result);
125+
126+
NSString *resultString = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
127+
XCTAssertTrue([resultString isEqualToString:@"00010203-0405-0607-0809-0A0B0C0D0E0F"]);
128+
}
129+
130+
@end

0 commit comments

Comments
 (0)