Skip to content

feat(dif): Add WASM support #22264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ six>=1.11.0,<1.12.0
sqlparse>=0.2.0,<0.3.0
statsd>=3.1.0,<3.2.0
structlog==17.1.0
symbolic>=7.3.5,<8.0.0
symbolic>=8.0.0,<9.0.0
toronado>=0.0.11,<0.1.0
ua-parser>=0.10.0,<0.11.0
unidiff>=0.5.4
Expand Down
1 change: 1 addition & 0 deletions src/sentry/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ def get_all_languages():
"application/x-elf-binary": "elf",
"application/x-dosexec": "pe",
"application/x-ms-pdb": "pdb",
"application/wasm": "wasm",
"text/x-proguard+plain": "proguard",
"application/x-sentry-bundle+zip": "sourcebundle",
}
Expand Down
6 changes: 6 additions & 0 deletions src/sentry/interfaces/stacktrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def to_python(cls, data, raw=False):
"image_addr",
"in_app",
"instruction_addr",
"addr_mode",
"lineno",
"module",
"package",
Expand Down Expand Up @@ -172,6 +173,7 @@ def to_json(self):
"symbol": self.symbol,
"symbol_addr": self.symbol_addr,
"instruction_addr": self.instruction_addr,
"addr_mode": self.addr_mode,
"trust": self.trust,
"in_app": self.in_app,
"context_line": self.context_line,
Expand Down Expand Up @@ -214,6 +216,10 @@ def get_api_context(self, is_public=False, pad_addr=None, platform=None):
}
if not is_public:
data["vars"] = self.vars

if self.addr_mode and self.addr_mode != "abs":
data["addrMode"] = self.addr_mode

# TODO(dcramer): abstract out this API
if self.data and "sourcemap" in data:
data.update(
Expand Down
63 changes: 59 additions & 4 deletions src/sentry/lang/native/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from sentry.stacktraces.processing import find_stacktraces_in_data
from sentry.utils.compat import zip

from symbolic import normalize_debug_id, ParseDebugIdError


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -73,6 +75,13 @@ def _merge_frame(new_frame, symbolicated):
new_frame["context_line"] = symbolicated["context_line"]
if symbolicated.get("post_context"):
new_frame["post_context"] = symbolicated["post_context"]

addr_mode = symbolicated.get("addr_mode")
if addr_mode is None:
new_frame.pop("addr_mode", None)
else:
new_frame["addr_mode"] = addr_mode

if symbolicated.get("status"):
frame_meta = new_frame.setdefault("data", {})
frame_meta["symbolicator_status"] = symbolicated["status"]
Expand Down Expand Up @@ -283,6 +292,51 @@ def _handles_frame(data, frame):
return is_native_platform(platform) and "instruction_addr" in frame


def get_frames_for_symbolication(frames, data, modules):
modules_by_debug_id = None
rv = []

for frame in reversed(frames):
if not _handles_frame(data, frame):
continue
s_frame = dict(frame)

# validate and expand addressing modes. If we can't validate and
# expand it, we keep None which is absolute. That's not great but
# at least won't do damage.
addr_mode = s_frame.pop("addr_mode", None)
sanitized_addr_mode = None

# None and abs mean absolute addressing explicitly.
if addr_mode in (None, "abs"):
pass
# this is relative addressing to module by index or debug id.
elif addr_mode.startswith("rel:"):
arg = addr_mode[4:]
idx = None

if modules_by_debug_id is None:
modules_by_debug_id = dict(
(x.get("debug_id"), idx) for idx, x in enumerate(modules)
)
try:
idx = modules_by_debug_id.get(normalize_debug_id(arg))
except ParseDebugIdError:
pass

if idx is None and arg.isdigit():
idx = int(arg)

if idx is not None:
sanitized_addr_mode = "rel:%d" % idx

if sanitized_addr_mode is not None:
s_frame["addr_mode"] = sanitized_addr_mode
rv.append(s_frame)

return rv


def process_payload(data):
project = Project.objects.get_from_cache(id=data["project"])

Expand All @@ -294,20 +348,21 @@ def process_payload(data):
if any(is_native_platform(x) for x in stacktrace.platforms)
]

modules = native_images_from_data(data)

stacktraces = [
{
"registers": sinfo.stacktrace.get("registers") or {},
"frames": [
f for f in reversed(sinfo.stacktrace.get("frames") or ()) if _handles_frame(data, f)
],
"frames": get_frames_for_symbolication(
sinfo.stacktrace.get("frames") or (), data, modules
),
}
for sinfo in stacktrace_infos
]

if not any(stacktrace["frames"] for stacktrace in stacktraces):
return

modules = native_images_from_data(data)
signal = signal_from_data(data)

response = symbolicator.process_payload(stacktraces=stacktraces, modules=modules, signal=signal)
Expand Down
3 changes: 1 addition & 2 deletions src/sentry/lang/native/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"elf", # Linux
"macho", # macOS, iOS
"pe", # Windows
"wasm", # WASM
)

# Default disables storing crash reports.
Expand All @@ -40,8 +41,6 @@ def is_native_image(image):
return (
bool(image)
and image.get("type") in NATIVE_IMAGE_TYPES
and image.get("image_addr") is not None
and image.get("image_size") is not None
and (image.get("debug_id") or image.get("id") or image.get("uuid")) is not None
)

Expand Down
4 changes: 3 additions & 1 deletion src/sentry/models/debugfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ def file_extension(self):
return ".pdb"
if self.file_format == "sourcebundle":
return ".src.zip"
if self.file_format == "wasm":
return ".wasm"

return ""

Expand Down Expand Up @@ -190,7 +192,7 @@ def create_dif_from_id(project, meta, fileobj=None, file=None):
"""
if meta.file_format == "proguard":
object_name = "proguard-mapping"
elif meta.file_format in ("macho", "elf", "pdb", "pe", "sourcebundle"):
elif meta.file_format in ("macho", "elf", "pdb", "pe", "wasm", "sourcebundle"):
object_name = meta.name
elif meta.file_format == "breakpad":
object_name = meta.name[:-4] if meta.name.endswith(".sym") else meta.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,13 @@ const DebugImage = React.memo(({image, orgId, projectId, showDetails, style}: Pr
<ImageInfoGroup>{renderIconElement()}</ImageInfoGroup>

<ImageInfoGroup>
<Formatted>{formatAddress(startAddress, IMAGE_ADDR_LEN)}</Formatted> &ndash;{' '}
<AddressDivider />
<Formatted>{formatAddress(endAddress, IMAGE_ADDR_LEN)}</Formatted>
{startAddress && endAddress ? (
<React.Fragment>
<Formatted>{formatAddress(startAddress, IMAGE_ADDR_LEN)}</Formatted> &ndash;{' '}
<AddressDivider />
<Formatted>{formatAddress(endAddress, IMAGE_ADDR_LEN)}</Formatted>
</React.Fragment>
) : null}
</ImageInfoGroup>

<ImageInfoGroup fullWidth>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type State = {
panelBodyHeight?: number;
};

function normalizeId(id: string | undefined): string {
return id ? id.trim().toLowerCase().replace(/[- ]/g, '') : '';
}

const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 81,
Expand Down Expand Up @@ -159,19 +163,24 @@ class DebugMeta extends React.PureComponent<Props, State> {
}

// When searching for an address, check for the address range of the image
// instead of an exact match.
// instead of an exact match. Note that images cannot be found by index
// if they are at 0x0. For those relative addressing has to be used.
if (searchTerm.indexOf('0x') === 0) {
const needle = parseAddress(searchTerm);
if (needle > 0) {
if (needle > 0 && image.image_addr !== '0x0') {
const [startAddress, endAddress] = getImageRange(image);
return needle >= startAddress && needle < endAddress;
}
}

// the searchTerm ending at "!" is the end of the ID search.
const relMatch = normalizeId(searchTerm).match(/^\s*(.*?)!/);
const idSearchTerm = normalizeId((relMatch && relMatch[1]) || searchTerm);

return (
// Prefix match for identifiers
(image.code_id?.toLowerCase() || '').indexOf(searchTerm) === 0 ||
(image.debug_id?.toLowerCase() || '').indexOf(searchTerm) === 0 ||
normalizeId(image.code_id).indexOf(idSearchTerm) === 0 ||
normalizeId(image.debug_id).indexOf(idSearchTerm) === 0 ||
// Any match for file paths
(image.code_file?.toLowerCase() || '').indexOf(searchTerm) >= 0 ||
(image.debug_file?.toLowerCase() || '').indexOf(searchTerm) >= 0
Expand Down Expand Up @@ -214,9 +223,27 @@ class DebugMeta extends React.PureComponent<Props, State> {
return undefined;
}

const searchTerm = this.state.filter.toLowerCase();
const searchTerm = normalizeId(this.state.filter.toLowerCase());
const relMatch = searchTerm.match(/^\s*(.*?)!(.*)$/);

return frames.find(frame => frame.instructionAddr?.toLowerCase() === searchTerm);
if (relMatch) {
const debugImages = this.getDebugImages().map(
(image, idx) => [idx, image] as [number, Image]
);
const filteredImages = debugImages.filter(([_, image]) => this.filterImage(image));
if (filteredImages.length === 1) {
return frames.find(frame => {
return (
frame.addrMode === `rel:${filteredImages[0][0]}` &&
frame.instructionAddr?.toLowerCase() === relMatch[2]
);
});
} else {
return undefined;
}
} else {
return frames.find(frame => frame.instructionAddr?.toLowerCase() === searchTerm);
}
}

getDebugImages() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ type State = {
isExpanded?: boolean;
};

function makeFilter(
addr: string,
addrMode: string | undefined,
image?: React.ComponentProps<typeof DebugImage>['image']
): string {
let filter = addr;
if (!(!addrMode || addrMode === 'abs') && image) {
filter = image.debug_id + '!' + filter;
}
return filter;
}

export class Line extends React.Component<Props, State> {
static defaultProps = {
isExpanded: false,
Expand Down Expand Up @@ -147,7 +159,12 @@ export class Line extends React.Component<Props, State> {

scrollToImage = event => {
event.stopPropagation(); // to prevent collapsing if collapsable
DebugMetaActions.updateFilter(this.props.data.instructionAddr);
const {instructionAddr, addrMode} = this.props.data;
if (instructionAddr) {
DebugMetaActions.updateFilter(
makeFilter(instructionAddr, addrMode, this.props.image)
);
}
scrollToElement('#packages');
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,18 @@ export default class StacktraceContent extends React.Component<Props, State> {
);
};

findImageForAddress(address: Frame['instructionAddr']) {
findImageForAddress(address: Frame['instructionAddr'], addrMode: Frame['addrMode']) {
const images = this.props.event.entries.find(entry => entry.type === 'debugmeta')
?.data?.images;

return images && address
? images.find(img => {
const [startAddress, endAddress] = getImageRange(img);
return address >= startAddress && address < endAddress;
? images.find((img, idx) => {
if (!addrMode || addrMode === 'abs') {
const [startAddress, endAddress] = getImageRange(img);
return address >= startAddress && address < endAddress;
} else {
return addrMode === `rel:${idx}`;
}
})
: null;
}
Expand Down Expand Up @@ -152,7 +156,10 @@ export default class StacktraceContent extends React.Component<Props, State> {

const maxLengthOfAllRelativeAddresses = data.frames.reduce(
(maxLengthUntilThisPoint, frame) => {
const correspondingImage = this.findImageForAddress(frame.instructionAddr);
const correspondingImage = this.findImageForAddress(
frame.instructionAddr,
frame.addrMode
);

try {
const relativeAddress = (
Expand Down Expand Up @@ -188,7 +195,7 @@ export default class StacktraceContent extends React.Component<Props, State> {
}

if (this.frameIsVisible(frame, nextFrame) && !repeatedFrame) {
const image = this.findImageForAddress(frame.instructionAddr);
const image = this.findImageForAddress(frame.instructionAddr, frame.addrMode);

frames.push(
<Line
Expand Down
1 change: 1 addition & 0 deletions src/sentry/static/sentry/app/types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,7 @@ export type Frame = {
function: string | null;
inApp: boolean;
instructionAddr: string | null;
addrMode?: string;
lineNo: number | null;
module: string | null;
package: string | null;
Expand Down
4 changes: 1 addition & 3 deletions tests/sentry/api/endpoints/test_dif_assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,4 @@ def test_dif_error_response(self):

assert response.status_code == 200, response.content
assert response.data[total_checksum]["state"] == ChunkFileState.ERROR
assert response.data[total_checksum]["detail"].startswith(
"Unsupported debug information file"
)
assert "unsupported object file format" in response.data[total_checksum]["detail"]