Skip to content

Commit bcf87cf

Browse files
committed
properly format the debug-id as an OLE GUID
1 parent fd208cf commit bcf87cf

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package io.sentry.android.core.internal.tombstone;
2+
3+
import androidx.annotation.Nullable;
4+
5+
/**
6+
* Used to convert a build/code-id hex string into a little-endian GUID string (from OLE) which is
7+
* the expected format for the debug-id.
8+
*
9+
* <p>Each component used in a GUID uses little-endian representation. But the last two components
10+
* (`clock_seq` and `node`) were represented in memory as eight individual bytes (which makes them
11+
* look like big endian when formatted as a string).
12+
*
13+
* <p>Conversion example from the sentry development docs:
14+
*
15+
* <pre>
16+
* f1c3bcc0 2798 65fe 3058 404b2831d9e6 4135386c
17+
* 32-bit 16 16 2x8 6x8 Ignored
18+
* LE LE LE LE LE
19+
* = = = = =
20+
* c0bcc3f1-9827-fe65-3058-404b2831d9e6
21+
* </pre>
22+
*
23+
* Note: Java bytes are signed. When promoted (e.g. during formatting or bit shifts), they
24+
* sign-extend to int, unlike uint8_t in C. We therefore mask with & 0xff to preserve the intended
25+
* unsigned byte values.
26+
*/
27+
public class OleGuidFormatter {
28+
public static String convert(final @Nullable String hex) {
29+
if (hex == null) {
30+
throw new NullPointerException("GUID conversion input hex string");
31+
}
32+
if ((hex.length() % 2) != 0) {
33+
throw new IllegalArgumentException("The GUID conversion input hex string has odd length");
34+
}
35+
if (hex.length() < 32) {
36+
throw new IllegalArgumentException(
37+
"Need at least 16 bytes (32 hex chars) to convert to GUID");
38+
}
39+
40+
final byte[] b = hexToBytes(hex);
41+
42+
final long timeLow = u32le(b, 0);
43+
final int timeMid = u16le(b, 4);
44+
final int timeHiAndVersion = u16le(b, 6);
45+
46+
return String.format(
47+
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
48+
timeLow,
49+
timeMid,
50+
timeHiAndVersion,
51+
// clock_seq_hi_and_reserved
52+
b[8] & 0xff,
53+
// clock_seq_low
54+
b[9] & 0xff,
55+
// node (6 MAC components)
56+
b[10] & 0xff,
57+
b[11] & 0xff,
58+
b[12] & 0xff,
59+
b[13] & 0xff,
60+
b[14] & 0xff,
61+
b[15] & 0xff);
62+
}
63+
64+
private static int u16le(byte[] b, int offset) {
65+
return (b[offset] & 0xff) | ((b[offset + 1] & 0xff) << 8);
66+
}
67+
68+
private static long u32le(byte[] b, int offset) {
69+
return (long) (b[offset] & 0xff)
70+
| ((long) (b[offset + 1] & 0xff) << 8)
71+
| ((long) (b[offset + 2] & 0xff) << 16)
72+
| ((long) (b[offset + 3] & 0xff) << 24);
73+
}
74+
75+
private static byte[] hexToBytes(String hex) {
76+
int numBytes = 16;
77+
byte[] result = new byte[numBytes];
78+
for (int byteIdx = 0; byteIdx < numBytes; byteIdx++) {
79+
int hi = Character.digit(hex.charAt(byteIdx * 2), numBytes);
80+
int lo = Character.digit(hex.charAt(byteIdx * 2 + 1), numBytes);
81+
result[byteIdx] = (byte) ((hi << 4) | lo);
82+
}
83+
return result;
84+
}
85+
}

sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs
198198
final DebugImage image = new DebugImage();
199199
image.setCodeId(module.getBuildId());
200200
image.setCodeFile(module.getMappingName());
201-
image.setDebugId(module.getBuildId());
201+
image.setDebugId(OleGuidFormatter.convert(module.getBuildId()));
202202
image.setImageAddr(formatHex(module.getBeginAddress()));
203203
image.setImageSize(module.getEndAddress() - module.getBeginAddress());
204204
image.setType("elf");
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.sentry.android.core.internal.tombstone
2+
3+
import java.lang.NullPointerException
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertFailsWith
7+
8+
class OleGuidFormatterTest {
9+
10+
@Test
11+
fun `a null input string throws`() {
12+
assertFailsWith<NullPointerException> { OleGuidFormatter.convert(null) }
13+
}
14+
15+
@Test
16+
fun `an input string with fewer than 32 characters throws`() {
17+
assertFailsWith<IllegalArgumentException> { OleGuidFormatter.convert("abcd") }
18+
}
19+
20+
@Test
21+
fun `an input string with odd number of characters throws`() {
22+
assertFailsWith<IllegalArgumentException> {
23+
OleGuidFormatter.convert("0123456789abcdef0123456789abcdef0")
24+
}
25+
}
26+
27+
@Test
28+
fun `an input example from the develop docs leads to the expected result`() {
29+
val input = "f1c3bcc0279865fe3058404b2831d9e64135386c"
30+
val output = OleGuidFormatter.convert(input)
31+
assertEquals("c0bcc3f1-9827-fe65-3058-404b2831d9e6", output)
32+
}
33+
}

0 commit comments

Comments
 (0)