plcrashreporter/Tests/PLCrashAsyncCompactUnwindEn...

1183 строки
54 KiB
Objective-C

/*
* Copyright (c) 2013 Plausible Labs Cooperative, Inc.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#import "PLCrashTestCase.h"
#import "PLCrashAsyncCompactUnwindEncoding.h"
#import "PLCrashAsyncMachOImage.h"
#import "PLCrashFeatureConfig.h"
#import "PLCrashCompatConstants.h"
#import <TargetConditionals.h>
#import <mach-o/fat.h>
#import <mach-o/arch.h>
#import <mach-o/dyld.h>
#if PLCRASH_FEATURE_UNWIND_COMPACT
#if TARGET_OS_MAC && (!TARGET_OS_IPHONE || TARGET_OS_MACCATALYST)
#define TEST_BINARY @"test.macosx"
#elif TARGET_OS_SIMULATOR
#define TEST_BINARY @"test.sim"
#elif TARGET_OS_IPHONE
#define TEST_BINARY @"test.ios"
#else
#error Unsupported target
#endif
/* The base PC value hard coded in our test CFE data */
#define BASE_PC 0
/* PC to use for the compact-common test */
#define PC_COMPACT_COMMON (BASE_PC+1)
#define PC_COMPACT_COMMON_ENCODING (UNWIND_X86_64_MODE_DWARF | PC_COMPACT_COMMON)
/* PC to use for the compact-private test */
#define PC_COMPACT_PRIVATE (BASE_PC+2)
#define PC_COMPACT_PRIVATE_ENCODING (UNWIND_X86_64_MODE_DWARF | PC_COMPACT_PRIVATE)
/* PC to use for the regular-common test */
#define PC_REGULAR (BASE_PC+10)
#define PC_REGULAR_ENCODING (UNWIND_X86_64_MODE_DWARF | PC_REGULAR)
/**
* @internal
*
* This code tests compact frame unwinding.
*/
@interface PLCrashAsyncCompactUnwindEncodingTests : PLCrashTestCase {
@private
/** The parsed Mach-O file (this will be a subset of _imageData) */
plcrash_async_macho_t _image;
/** The mapped unwind data */
plcrash_async_mobject_t _unwind_mobj;
/** The CFE reader */
plcrash_async_cfe_reader_t _reader;
}
@end
@implementation PLCrashAsyncCompactUnwindEncodingTests
- (void) setUp {
/*
* Warning: This code assumes 1:1 correspondance between vmaddr/vmsize and foffset/fsize in the loaded binary.
* This is currently the case with our test binaries, but it could possibly change in the future. To handle this,
* one would either need to:
* - Implement 'real' segment loading, ala https://github.com/landonf/libevil_patch/blob/b80ebf4c0442f234c4f3f9ec180a2f873c5e2559/libevil/libevil.m#L253
* or
* - Add a 'file mode' to the Mach-O parser that causes it to use file offsets rather than VM offsets.
* or
* - Don't bother to load all the segments properly, just map the CFE data.
*
* I didn't implement the file mode for the Mach-O parser as I'd like to keep that code as simple as possible,
* given that it runs in a privileged crash time position, and 'file' mode is only required for unit tests.
*
* Performing segment loading or parsing the Mach-O binary isn't much work, so I'll probably just do that, and then
* this comment can go away.
*/
/* Load and parse the Mach-o image. */
plcrash_error_t err;
NSData *mappedImage = [self nativeBinaryFromTestResource: TEST_BINARY];
err = plcrash_nasync_macho_init(&_image, mach_task_self(), [TEST_BINARY UTF8String], (pl_vm_address_t) [mappedImage bytes]);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to initialize Mach-O parser");
/* Map the unwind section */
err = plcrash_async_macho_map_section(&_image, SEG_TEXT, "__unwind_info", &_unwind_mobj);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to map unwind info");
/* Initialize the CFE reader */
#if !defined(__i386__) || !defined(__x86_64__) || !defined(__arm64__)
/* CFE is currently only supported for x86/x86-64/arm64, but our target binaries are not architecture specific;
* we fudge the type reported to the reader to allow us to test the reader on ARM32 anyway. */
cpu_type_t cputype = CPU_TYPE_X86;
#else
cpu_type_t cputype = _image.byteorder->swap32(_image.header.cputype);
#endif
err = plcrash_async_cfe_reader_init(&_reader, &_unwind_mobj, cputype);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to initialize CFE reader");
}
- (void) tearDown {
plcrash_nasync_macho_free(&_image);
plcrash_async_mobject_free(&_unwind_mobj);
plcrash_async_cfe_reader_free(&_reader);
}
#define EXTRACT_BITS(value, mask) ((value >> __builtin_ctz(mask)) & (((1 << __builtin_popcount(mask)))-1))
#define INSERT_BITS(bits, mask) ((bits << __builtin_ctz(mask)) & mask)
/**
* Test handling of NULL encoding.
*/
- (void) testX86DecodeNULLEncoding {
plcrash_async_cfe_entry_t entry;
STAssertEquals(plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86, 0x0), PLCRASH_ESUCCESS, @"Should return NOTFOUND for NULL encoding");
STAssertEquals(plcrash_async_cfe_entry_type(&entry), PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE, @"Incorrect CFE type");
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
}
/**
* Test handling of sparse register lists. These are only supported for the frame encodings; the 10-bit packed
* encoding format does not support sparse lists.
*
* It's unclear as to whether these actually ever occur in the wild.
*/
- (void) testX86SparseRegisterDecoding {
plcrash_async_cfe_entry_t entry;
/* x86 handling */
const uint32_t encoded_regs = UNWIND_X86_REG_ESI | (UNWIND_X86_REG_EDX << 3) | (UNWIND_X86_REG_ECX << 9);
uint32_t encoding = UNWIND_X86_MODE_EBP_FRAME | INSERT_BITS(encoded_regs, UNWIND_X86_EBP_FRAME_REGISTERS);
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
/* Extract the registers. Up to 5 may be encoded */
plcrash_regnum_t expected_reg[] = {
PLCRASH_X86_ESI,
PLCRASH_X86_EDX,
PLCRASH_REG_INVALID,
PLCRASH_X86_ECX
};
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(reg_count, (uint32_t) (sizeof(expected_reg) / sizeof(expected_reg[0])), @"Incorrect register count extracted");
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
for (uint32_t i = 0; i < reg_count; i++) {
STAssertEquals(reg[i], expected_reg[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an x86 EBP frame encoding.
*/
- (void) testX86DecodeFrame {
/* Create a frame encoding, with registers saved at ebp-1020 bytes */
const uint32_t encoded_reg_ebp_offset = 1020;
const uint32_t encoded_regs = UNWIND_X86_REG_ESI |
(UNWIND_X86_REG_EDX << 3) |
(UNWIND_X86_REG_ECX << 6);
uint32_t encoding = UNWIND_X86_MODE_EBP_FRAME |
INSERT_BITS(encoded_reg_ebp_offset/4, UNWIND_X86_EBP_FRAME_OFFSET) |
INSERT_BITS(encoded_regs, UNWIND_X86_EBP_FRAME_REGISTERS);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
uint32_t reg_ebp_offset = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(reg_ebp_offset, -encoded_reg_ebp_offset, @"Incorrect offset extracted");
STAssertEquals(reg_count, (uint32_t)3, @"Incorrect register count extracted");
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
/* Extract the registers. Up to 5 may be encoded */
plcrash_regnum_t expected_reg[] = {
PLCRASH_X86_ESI,
PLCRASH_X86_EDX,
PLCRASH_X86_ECX
};
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
for (uint32_t i = 0; i < 3; i++) {
STAssertEquals(reg[i], expected_reg[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
- (void) verifyFramelessRegDecode: (uint32_t) permutedRegisters
count: (uint32_t) count
expectedRegisters: (const uint32_t[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX]) expectedRegisters
{
/* Verify that our encoder generates the same result */
STAssertEquals(permutedRegisters, plcrash_async_cfe_register_encode(expectedRegisters, count), @"Incorrect internal encoding for count %" PRId32, count);
/* Extract and verify the registers */
uint32_t regs[count];
STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_cfe_register_decode(permutedRegisters, count, regs), @"Register decode returned an error");
for (uint32_t i = 0; i < count; i++) {
STAssertEquals(regs[i], expectedRegisters[i], @"Incorrect register value extracted for position %" PRId32, i);
}
}
/**
* Decode an x86 immediate 'frameless' encoding.
*/
- (void) testX86DecodeFramelessImmediate {
/* Create a frame encoding, with registers saved at ebp-1020 bytes */
const uint32_t encoded_stack_size = 1020;
const uint32_t encoded_regs[] = { UNWIND_X86_REG_ESI, UNWIND_X86_REG_EDX, UNWIND_X86_REG_ECX };
const uint32_t encoded_regs_count = sizeof(encoded_regs) / sizeof(encoded_regs[0]);
const uint32_t encoded_regs_permutation = plcrash_async_cfe_register_encode(encoded_regs, encoded_regs_count);
uint32_t encoding = UNWIND_X86_MODE_STACK_IMMD |
INSERT_BITS(encoded_stack_size/4, UNWIND_X86_FRAMELESS_STACK_SIZE) |
INSERT_BITS(encoded_regs_count, UNWIND_X86_FRAMELESS_STACK_REG_COUNT) |
INSERT_BITS(encoded_regs_permutation, UNWIND_X86_FRAMELESS_STACK_REG_PERMUTATION);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
uint32_t stack_size = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(stack_size, encoded_stack_size, @"Incorrect stack size decoded");
STAssertEquals(reg_count, encoded_regs_count, @"Incorrect register count decoded");
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
/* Verify the register decoding */
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
const plcrash_regnum_t expected_regs[] = { PLCRASH_X86_ESI, PLCRASH_X86_EDX, PLCRASH_X86_ECX };
for (uint32_t i = 0; i < 3; i++) {
STAssertEquals(reg[i], expected_regs[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an x86 indirect 'frameless' encoding.
*/
- (void) testX86DecodeFramelessIndirect {
/* Create a frame encoding, with registers saved at ebp-24 bytes */
const uint32_t encoded_stack_size = 20;
const uint32_t encoded_regs[] = { UNWIND_X86_REG_ESI, UNWIND_X86_REG_EDX, UNWIND_X86_REG_ECX };
const uint32_t encoded_regs_count = sizeof(encoded_regs) / sizeof(encoded_regs[0]);
const uint32_t encoded_regs_permutation = plcrash_async_cfe_register_encode(encoded_regs, encoded_regs_count);
const uint32_t encoded_stack_adjust = 4;
uint32_t encoding = UNWIND_X86_MODE_STACK_IND |
INSERT_BITS(encoded_stack_size, UNWIND_X86_FRAMELESS_STACK_SIZE) |
INSERT_BITS(encoded_regs_count, UNWIND_X86_FRAMELESS_STACK_REG_COUNT) |
INSERT_BITS(encoded_regs_permutation, UNWIND_X86_FRAMELESS_STACK_REG_PERMUTATION) |
INSERT_BITS(encoded_stack_adjust/4, UNWIND_X86_FRAMELESS_STACK_ADJUST);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
uint32_t stack_size = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
uint32_t stack_adjust = plcrash_async_cfe_entry_stack_adjustment(&entry);
STAssertEquals(stack_size, encoded_stack_size, @"Incorrect stack size decoded");
STAssertEquals(reg_count, encoded_regs_count, @"Incorrect register count decoded");
STAssertEquals(stack_adjust, encoded_stack_adjust, @"Incorrect stack adjustment decoded");
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
/* Verify the register decoding */
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
const plcrash_regnum_t expected_regs[] = { PLCRASH_X86_ESI, PLCRASH_X86_EDX, PLCRASH_X86_ECX };
for (uint32_t i = 0; i < 3; i++) {
STAssertEquals(reg[i], expected_regs[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an x86 DWARF encoding.
*/
- (void) testX86DecodeDWARF {
/* Create a frame encoding, with registers saved at ebp-1020 bytes */
const uint32_t encoded_dwarf_offset = 1020;
uint32_t encoding = UNWIND_X86_MODE_DWARF |
INSERT_BITS(encoded_dwarf_offset, UNWIND_X86_DWARF_SECTION_OFFSET);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
uint32_t dwarf_offset = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
STAssertEquals(dwarf_offset, encoded_dwarf_offset, @"Incorrect dwarf offset decoded");
plcrash_async_cfe_entry_free(&entry);
}
/**
* Test handling of NULL encoding.
*/
- (void) testX86_64DecodeNULLEncoding {
plcrash_async_cfe_entry_t entry;
STAssertEquals(plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, 0x0), PLCRASH_ESUCCESS, @"Should return success for NULL encoding");
STAssertEquals(plcrash_async_cfe_entry_type(&entry), PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE, @"Incorrect CFE type");
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
}
/**
* Test handling of sparse register lists. These are only supported for the frame encodings; the 10-bit packed
* encoding format does not support sparse lists.
*
* It's unclear as to whether these actually ever occur in the wild.
*/
- (void) testX86_64SparseRegisterDecoding {
plcrash_async_cfe_entry_t entry;
/* x86 handling */
const uint32_t encoded_regs = UNWIND_X86_64_REG_RBX | (UNWIND_X86_64_REG_R12 << 3) | (UNWIND_X86_64_REG_R13 << 9);
uint32_t encoding = UNWIND_X86_64_MODE_RBP_FRAME | INSERT_BITS(encoded_regs, UNWIND_X86_64_RBP_FRAME_REGISTERS);
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
/* Extract the registers. Up to 5 may be encoded */
plcrash_regnum_t expected_reg[] = {
PLCRASH_X86_64_RBX,
PLCRASH_X86_64_R12,
PLCRASH_REG_INVALID,
PLCRASH_X86_64_R13
};
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(reg_count, (uint32_t) (sizeof(expected_reg) / sizeof(expected_reg[0])), @"Incorrect register count extracted");
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
for (uint32_t i = 0; i < reg_count; i++) {
STAssertEquals(reg[i], expected_reg[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an x86-64 RBP frame encoding.
*/
- (void) testX86_64DecodeFrame {
/* Create a frame encoding, with registers saved at rbp-1020 bytes */
const uint32_t encoded_reg_rbp_offset = 1016;
const uint32_t encoded_regs = UNWIND_X86_64_REG_R12 |
(UNWIND_X86_64_REG_R13 << 3) |
(UNWIND_X86_64_REG_R14 << 6);
uint32_t encoding = UNWIND_X86_64_MODE_RBP_FRAME |
INSERT_BITS(encoded_reg_rbp_offset/8, UNWIND_X86_64_RBP_FRAME_OFFSET) |
INSERT_BITS(encoded_regs, UNWIND_X86_64_RBP_FRAME_REGISTERS);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
uint32_t reg_ebp_offset = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(reg_ebp_offset, -encoded_reg_rbp_offset, @"Incorrect offset extracted");
STAssertEquals(reg_count, (uint32_t)3, @"Incorrect register count extracted");
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
/* Extract the registers. Up to 5 may be encoded */
plcrash_regnum_t expected_reg[] = {
PLCRASH_X86_64_R12,
PLCRASH_X86_64_R13,
PLCRASH_X86_64_R14
};
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
for (uint32_t i = 0; i < 3; i++) {
STAssertEquals(reg[i], expected_reg[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an x86-64 immediate 'frameless' encoding.
*/
- (void) testX86_64DecodeFramelessImmediate {
/* Create a frame encoding, with registers saved at ebp-1020 bytes */
const uint32_t encoded_stack_size = 1016;
const uint32_t encoded_regs[] = { UNWIND_X86_64_REG_R12, UNWIND_X86_64_REG_R13, UNWIND_X86_64_REG_R14 };
const uint32_t encoded_regs_count = sizeof(encoded_regs) / sizeof(encoded_regs[0]);
const uint32_t encoded_regs_permutation = plcrash_async_cfe_register_encode(encoded_regs, encoded_regs_count);
uint32_t encoding = UNWIND_X86_64_MODE_STACK_IMMD |
INSERT_BITS(encoded_stack_size/8, UNWIND_X86_64_FRAMELESS_STACK_SIZE) |
INSERT_BITS(encoded_regs_count, UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT) |
INSERT_BITS(encoded_regs_permutation, UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
uint32_t stack_size = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(stack_size, encoded_stack_size, @"Incorrect stack size decoded");
STAssertEquals(reg_count, encoded_regs_count, @"Incorrect register count decoded");
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
/* Verify the register decoding */
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
const plcrash_regnum_t expected_regs[] = { PLCRASH_X86_64_R12, PLCRASH_X86_64_R13, PLCRASH_X86_64_R14 };
for (uint32_t i = 0; i < 3; i++) {
STAssertEquals(reg[i], expected_regs[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an x86-64 indirect 'frameless' encoding.
*/
- (void) testX86_64DecodeFramelessIndirect {
/* Create a frame encoding, with registers saved at ebp-24 bytes */
const uint32_t encoded_stack_size = 20;
const uint32_t encoded_regs[] = { UNWIND_X86_64_REG_R12, UNWIND_X86_64_REG_R13, UNWIND_X86_64_REG_R14 };
const uint32_t encoded_regs_count = sizeof(encoded_regs) / sizeof(encoded_regs[0]);
const uint32_t encoded_regs_permutation = plcrash_async_cfe_register_encode(encoded_regs, encoded_regs_count);
uint32_t encoding = UNWIND_X86_64_MODE_STACK_IND |
INSERT_BITS(encoded_stack_size, UNWIND_X86_64_FRAMELESS_STACK_SIZE) |
INSERT_BITS(encoded_regs_count, UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT) |
INSERT_BITS(encoded_regs_permutation, UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
uint32_t stack_size = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(stack_size, encoded_stack_size, @"Incorrect stack size decoded");
STAssertEquals(reg_count, encoded_regs_count, @"Incorrect register count decoded");
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
/* Verify the register decoding */
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
const plcrash_regnum_t expected_regs[] = { PLCRASH_X86_64_R12, PLCRASH_X86_64_R13, PLCRASH_X86_64_R14 };
for (uint32_t i = 0; i < 3; i++) {
STAssertEquals(reg[i], expected_regs[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an x86-64 DWARF encoding.
*/
- (void) testX86_64DecodeDWARF {
/* Create a frame encoding, with registers saved at ebp-1020 bytes */
const uint32_t encoded_dwarf_offset = 1016;
uint32_t encoding = UNWIND_X86_64_MODE_DWARF |
INSERT_BITS(encoded_dwarf_offset, UNWIND_X86_64_DWARF_SECTION_OFFSET);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
uint32_t dwarf_offset = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
STAssertEquals(dwarf_offset, encoded_dwarf_offset, @"Incorrect dwarf offset decoded");
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an ARM64 FP frame encoding.
*/
- (void) testARM64DecodeFrame {
/* Create a frame encoding */
uint32_t encoding = UNWIND_ARM64_MODE_FRAME |
UNWIND_ARM64_FRAME_X21_X22_PAIR |
UNWIND_ARM64_FRAME_X25_X26_PAIR;
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_ARM64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
int32_t reg_offset = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(reg_count, (uint32_t)4, @"Incorrect register count extracted");
STAssertEquals(reg_offset, (int32_t)-32, @"Incorrect register offset extracted (wanted -32, got %"PRId32")", reg_offset);
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
/* Extract the registers. */
plcrash_regnum_t expected_reg[] = {
PLCRASH_ARM64_X26,
PLCRASH_ARM64_X25,
PLCRASH_ARM64_X22,
PLCRASH_ARM64_X21,
};
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
for (uint32_t i = 0; i < (sizeof(expected_reg)/sizeof(expected_reg[0])); i++) {
STAssertEquals(reg[i], expected_reg[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an ARM64 'frameless' encoding.
*/
- (void) testARM64DecodeFrameless {
/* Create a frame encoding, with registers saved at sp+1008 bytes */
const uint32_t encoded_stack_size = 1008;
uint32_t encoding = UNWIND_ARM64_MODE_FRAMELESS |
INSERT_BITS((encoded_stack_size/16), UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK) |
UNWIND_ARM64_FRAME_X25_X26_PAIR |
UNWIND_ARM64_FRAME_X27_X28_PAIR;
uint32_t encoded_regs_count = 4;
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_ARM64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
uint32_t stack_size = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
uint32_t reg_count = plcrash_async_cfe_entry_register_count(&entry);
STAssertEquals(stack_size, encoded_stack_size, @"Incorrect stack size decoded");
STAssertEquals(reg_count, encoded_regs_count, @"Incorrect register count decoded");
/* Verify the return address register value */
STAssertEquals((plcrash_regnum_t)PLCRASH_ARM64_LR, plcrash_async_cfe_entry_return_address_register(&entry), @"Incorrect return address register set");
/* Verify the register decoding */
plcrash_regnum_t reg[reg_count];
plcrash_async_cfe_entry_register_list(&entry, reg);
plcrash_regnum_t expected_reg[] = {
PLCRASH_ARM64_X28,
PLCRASH_ARM64_X27,
PLCRASH_ARM64_X26,
PLCRASH_ARM64_X25,
};
for (uint32_t i = 0; i < (sizeof(expected_reg)/sizeof(expected_reg[0])); i++) {
STAssertEquals(reg[i], expected_reg[i], @"Incorrect register value extracted for position %" PRId32, i);
}
plcrash_async_cfe_entry_free(&entry);
}
/**
* Decode an ARM64 DWARF encoding.
*/
- (void) testARM64DecodeDWARF {
/* Create a frame encoding */
const uint32_t encoded_dwarf_offset = 1020;
uint32_t encoding = UNWIND_ARM64_MODE_DWARF |
INSERT_BITS(encoded_dwarf_offset, UNWIND_ARM64_DWARF_SECTION_OFFSET);
/* Try decoding it */
plcrash_async_cfe_entry_t entry;
plcrash_error_t res = plcrash_async_cfe_entry_init(&entry, CPU_TYPE_ARM64, encoding);
STAssertEquals(res, PLCRASH_ESUCCESS, @"Failed to decode entry");
STAssertEquals(PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF, plcrash_async_cfe_entry_type(&entry), @"Incorrect entry type");
STAssertEquals((plcrash_regnum_t)PLCRASH_REG_INVALID, plcrash_async_cfe_entry_return_address_register(&entry), @"Return address register set");
uint32_t dwarf_offset = (uint32_t) plcrash_async_cfe_entry_stack_offset(&entry);
STAssertEquals(dwarf_offset, encoded_dwarf_offset, @"Incorrect dwarf offset decoded");
plcrash_async_cfe_entry_free(&entry);
}
/**
* Test decoding of a single non-zero permuted register.
*
* An implementation bug in plcrash_async_cfe_register_decode() would always result in the last register element
* of a 1-length register list being set to 0.
*/
- (void) testPermutedRegisterEncodeOneNonZero {
const uint32_t expected[] = { UNWIND_X86_REG_ESI };
uint32_t decoded[sizeof(expected)/sizeof(expected[0])];
uint32_t encoded = plcrash_async_cfe_register_encode(expected, 1);
STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_cfe_register_decode(encoded, 1, decoded), @"Decode returned an error");
STAssertEquals(expected[0], decoded[0], @"Failed to decode register");
}
/**
* Test passing an invalid count to plcrash_async_cfe_register_decode()
*/
- (void) testPermuttedRegisterDecodeInvalidCount {
uint32_t registers[PLCRASH_ASYNC_CFE_PERMUTATION_REGISTER_MAX+1];
STAssertNotEquals(PLCRASH_ESUCCESS, plcrash_async_cfe_register_decode(0, PLCRASH_ASYNC_CFE_PERMUTATION_REGISTER_MAX+1, registers), @"Decoding of a too-large count did not return an error");
}
/**
* Verify encoding+decoding of permuted frameless registers, verifying all supported lengths. The test cases were
* all extracted from code generated by clang.
*/
- (void) testPermutedRegisterEncoding {
#define PL_EXBIT(v) EXTRACT_BITS(v, UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION)
/* 1 item */
{
const uint32_t expected[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX] = { UNWIND_X86_64_REG_RBX };
[self verifyFramelessRegDecode: PL_EXBIT(0x02020400) count: 1 expectedRegisters: expected];
}
/* 2 items */
{
const uint32_t expected[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX] = {
UNWIND_X86_64_REG_R15,
UNWIND_X86_64_REG_R14
};
[self verifyFramelessRegDecode: PL_EXBIT(0x02030817) count: 2 expectedRegisters: expected];
}
/* 3 items */
{
const uint32_t expected[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX] = {
UNWIND_X86_64_REG_RBX,
UNWIND_X86_64_REG_R14,
UNWIND_X86_64_REG_R15
};
[self verifyFramelessRegDecode: PL_EXBIT(0x02040C0A) count: 3 expectedRegisters: expected];
}
/* 4 items */
{
const uint32_t expected[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX] = {
UNWIND_X86_64_REG_RBX,
UNWIND_X86_64_REG_R12,
UNWIND_X86_64_REG_R14,
UNWIND_X86_64_REG_R15
};
[self verifyFramelessRegDecode: PL_EXBIT(0x02051004) count: 4 expectedRegisters: expected];
}
/* 5 items */
{
const uint32_t expected[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX] = {
UNWIND_X86_64_REG_RBX,
UNWIND_X86_64_REG_R12,
UNWIND_X86_64_REG_R13,
UNWIND_X86_64_REG_R14,
UNWIND_X86_64_REG_R15
};
[self verifyFramelessRegDecode: PL_EXBIT(0x02071800) count: 5 expectedRegisters: expected];
}
/* 6 items */
{
const uint32_t expected[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX] = {
UNWIND_X86_64_REG_RBX,
UNWIND_X86_64_REG_R12,
UNWIND_X86_64_REG_R13,
UNWIND_X86_64_REG_R14,
UNWIND_X86_64_REG_R15,
UNWIND_X86_64_REG_RBP
};
[self verifyFramelessRegDecode: PL_EXBIT(0x02071800) count: 6 expectedRegisters: expected];
}
#undef PL_EXBIT
}
/**
* Test reading of a PC, compressed, with a common encoding.
*/
- (void) testReadCompressedCommonEncoding {
pl_vm_address_t function_base;
plcrash_error_t err;
uint32_t encoding;
err = plcrash_async_cfe_reader_find_pc(&_reader, PC_COMPACT_COMMON, &function_base, &encoding);
STAssertEquals(PLCRASH_ESUCCESS, err, @"Failed to locate CFE entry");
STAssertEquals(function_base, (pl_vm_address_t)PC_COMPACT_COMMON, @"Incorrect function base returned");
STAssertEquals(encoding, (uint32_t)PC_COMPACT_COMMON_ENCODING, @"Incorrect encoding returned");
}
/**
* Test reading of a PC, compressed, with a private encoding.
*/
- (void) testReadCompressedEncoding {
pl_vm_address_t function_base;
plcrash_error_t err;
uint32_t encoding;
err = plcrash_async_cfe_reader_find_pc(&_reader, PC_COMPACT_PRIVATE, &function_base, &encoding);
STAssertEquals(PLCRASH_ESUCCESS, err, @"Failed to locate CFE entry");
STAssertEquals(function_base, (pl_vm_address_t)PC_COMPACT_PRIVATE, @"Incorrect function base returned");
STAssertEquals(encoding, (uint32_t)PC_COMPACT_PRIVATE_ENCODING, @"Incorrect encoding returned");
}
/**
* Test reading of a PC, regular, with a common encoding.
*/
- (void) testReadRegularEncoding {
pl_vm_address_t function_base;
plcrash_error_t err;
uint32_t encoding;
err = plcrash_async_cfe_reader_find_pc(&_reader, PC_REGULAR, &function_base, &encoding);
STAssertEquals(PLCRASH_ESUCCESS, err, @"Failed to locate CFE entry");
STAssertEquals(function_base, (pl_vm_address_t)PC_REGULAR, @"Incorrect function base returned");
STAssertEquals(encoding, (uint32_t)PC_REGULAR_ENCODING, @"Incorrect encoding returned");
}
/*
* The following tests can only be run with ARM64 thread state support.
*/
#if PLCRASH_ASYNC_THREAD_ARM_SUPPORT
- (void) testARM64_ApplyFramePTRState {
plcrash_async_cfe_entry_t entry;
plcrash_async_thread_state_t ts;
/* Set up a faux frame */
uint64_t stackframe[] = {
12, // x22
13, // x21
14, // x20
15, // x19
1, // fp
2, // lr
};
/* Create a frame encoding. */
uint32_t encoding = UNWIND_ARM64_MODE_FRAME |
UNWIND_ARM64_FRAME_X19_X20_PAIR |
UNWIND_ARM64_FRAME_X21_X22_PAIR;
STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_cfe_entry_init(&entry, CPU_TYPE_ARM64, encoding), @"Failed to initialize CFE entry");
/* Initialize default thread state */
plcrash_greg_t stack_addr = (plcrash_greg_t) &stackframe[4]; // fp
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_ARM64), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
plcrash_async_thread_state_set_reg(&ts, PLCRASH_REG_FP, stack_addr);
/* Apply! */
plcrash_async_thread_state_t nts;
plcrash_error_t err = plcrash_async_cfe_entry_apply(mach_task_self(), 0x0, &ts, &entry, &nts);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to apply state to thread");
/* Verify! */
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_SP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_FP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_PC), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X19), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X20), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X21), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X22), @"Missing expected register");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_SP), (plcrash_greg_t)stack_addr+(16), @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_FP), (plcrash_greg_t)1, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_PC), (plcrash_greg_t)2, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X19), (plcrash_greg_t)15, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X20), (plcrash_greg_t)14, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X21), (plcrash_greg_t)13, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X22), (plcrash_greg_t)12, @"Incorrect register value");
plcrash_async_cfe_entry_free(&entry);
}
/**
* Apply an ARM64 frameless encoding.
*/
- (void) testARM64_ApplyFramelessPTRState {
plcrash_async_cfe_entry_t entry;
plcrash_async_thread_state_t ts;
/* Set up a faux frame */
uint64_t stackframe[] = {
0, // padding to exercise stack size computation
0, // padding
12, // x22
13, // x21
14, // x20
15, // x19
};
/* Create a frame encoding. */
/* Create a frame encoding, with registers saved at (restored sp)-32 bytes */
const uint32_t encoded_stack_size = sizeof(stackframe);
uint32_t encoding = UNWIND_ARM64_MODE_FRAMELESS |
INSERT_BITS(encoded_stack_size/16, UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK) |
UNWIND_ARM64_FRAME_X19_X20_PAIR |
UNWIND_ARM64_FRAME_X21_X22_PAIR;
STAssertEquals(plcrash_async_cfe_entry_init(&entry, CPU_TYPE_ARM64, encoding), PLCRASH_ESUCCESS, @"Failed to decode entry");
/* Initialize default thread state */
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_ARM64), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
plcrash_async_thread_state_set_reg(&ts, PLCRASH_REG_SP, (plcrash_greg_t) &stackframe);
plcrash_async_thread_state_set_reg(&ts, PLCRASH_ARM64_LR, 2);
/* Apply */
plcrash_async_thread_state_t nts;
plcrash_error_t err = plcrash_async_cfe_entry_apply(mach_task_self(), 0x0, &ts, &entry, &nts);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to apply state to thread");
/* Verify */
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_SP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_PC), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X19), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X20), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X21), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_ARM64_X22), @"Missing expected register");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_SP), ((plcrash_greg_t)&stackframe) + encoded_stack_size, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_PC), (plcrash_greg_t)2, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X19), (plcrash_greg_t)15, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X20), (plcrash_greg_t)14, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X21), (plcrash_greg_t)13, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_ARM64_X22), (plcrash_greg_t)12, @"Incorrect register value");
plcrash_async_cfe_entry_free(&entry);
}
#endif
/*
* The iOS SDK does not provide the thread state APIs necessary
* to perform the x86 tests on ARM
*/
#if PLCRASH_ASYNC_THREAD_X86_SUPPORT
- (void) testx86_64_ApplyFramePTRState {
plcrash_async_cfe_entry_t entry;
plcrash_async_thread_state_t ts;
/* Set up a faux frame */
uint64_t stackframe[] = {
12, // r12
13, // r13
0, // sparse slot
14, // r14
1, // rbp
2, // ret addr
};
/* Create a frame encoding, with registers saved at rbp-32 bytes. We insert
* a sparse slot to test the sparse handling */
const uint32_t encoded_reg_rbp_offset = 32;
const uint32_t encoded_regs = UNWIND_X86_64_REG_R12 |
(UNWIND_X86_64_REG_R13 << 3) |
0 << 6 /* SPARSE */ |
(UNWIND_X86_64_REG_R14 << 9);
uint32_t encoding = UNWIND_X86_64_MODE_RBP_FRAME |
INSERT_BITS(encoded_reg_rbp_offset/8, UNWIND_X86_64_RBP_FRAME_OFFSET) |
INSERT_BITS(encoded_regs, UNWIND_X86_64_RBP_FRAME_REGISTERS);
STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding), @"Failed to initialize CFE entry");
/* Initialize default thread state */
plcrash_greg_t stack_addr = (plcrash_greg_t) &stackframe[4]; // rbp
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86_64), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
plcrash_async_thread_state_set_reg(&ts, PLCRASH_REG_FP, stack_addr);
/* Apply! */
plcrash_async_thread_state_t nts;
plcrash_error_t err = plcrash_async_cfe_entry_apply(mach_task_self(), 0x0, &ts, &entry, &nts);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to apply state to thread");
/* Verify! */
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RSP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RBP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RIP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R12), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R13), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R14), @"Missing expected register");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RSP), (plcrash_greg_t)stack_addr+(16), @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RBP), (plcrash_greg_t)1, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RIP), (plcrash_greg_t)2, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R12), (plcrash_greg_t)12, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R13), (plcrash_greg_t)13, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R14), (plcrash_greg_t)14, @"Incorrect register value");
plcrash_async_cfe_entry_free(&entry);
}
/* This test requires storing local pointers to the host's stack in 32-bit x86's thread state; it can only be run on a 32-bit host,
* as the 64-bit stack pointers may exceed the UINT32_MAX. */
#ifndef __LP64__
- (void) testx86_32_ApplyFramePTRState {
plcrash_async_cfe_entry_t entry;
plcrash_async_thread_state_t ts;
/* Set up a faux frame */
uint32_t stackframe[] = {
12, // ebx
13, // ecx
0, // sparse slot
14, // edi
1, // ebp
2, // ret addr
};
/* Create a frame encoding, with registers saved at ebp-16 bytes. We insert
* a sparse slot to test the sparse handling */
const uint32_t encoded_reg_ebp_offset = 16;
const uint32_t encoded_regs = UNWIND_X86_REG_EBX |
(UNWIND_X86_REG_ECX << 3) |
0 << 6 /* SPARSE */ |
(UNWIND_X86_REG_EDI << 9);
uint32_t encoding = UNWIND_X86_MODE_EBP_FRAME |
INSERT_BITS(encoded_reg_ebp_offset/4, UNWIND_X86_EBP_FRAME_OFFSET) |
INSERT_BITS(encoded_regs, UNWIND_X86_EBP_FRAME_REGISTERS);
STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86, encoding), @"Failed to initialize CFE entry");
/* Initialize default thread state */
plcrash_greg_t stack_addr = &stackframe[4]; // ebp
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
plcrash_async_thread_state_set_reg(&ts, PLCRASH_REG_FP, stack_addr);
/* Apply! */
plcrash_async_thread_state_t nts;
plcrash_error_t err = plcrash_async_cfe_entry_apply(mach_task_self(), 0x0, &ts, &entry, &nts);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to apply state to thread");
/* Verify! */
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_ESP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_EBP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_EIP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_EBX), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_ECX), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_EDI), @"Missing expected register");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_ESP), (plcrash_greg_t)stack_addr+(8), @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_EBP), (plcrash_greg_t)1, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_EIP), (plcrash_greg_t)2, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_EBX), (plcrash_greg_t)12, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_ECX), (plcrash_greg_t)13, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_EDI), (plcrash_greg_t)14, @"Incorrect register value");
plcrash_async_cfe_entry_free(&entry);
}
#endif /* !__LP64__ */
/**
* Apply an x86-64 immediate 'frameless' encoding.
*/
- (void) testX86_64_ApplyFramePTRState_IMD {
plcrash_async_cfe_entry_t entry;
plcrash_async_thread_state_t ts;
/* Set up a faux frame */
uint64_t stackframe[] = {
10, // rbp
12, // r12
13, // r13
14, // r14
2, // ret addr
};
/* Create a frame encoding, with registers saved at esp-32 bytes */
const uint32_t encoded_stack_size = 40;
const uint32_t encoded_regs[] = { UNWIND_X86_64_REG_RBP, UNWIND_X86_64_REG_R12, UNWIND_X86_64_REG_R13, UNWIND_X86_64_REG_R14 };
const uint32_t encoded_regs_count = sizeof(encoded_regs) / sizeof(encoded_regs[0]);
const uint32_t encoded_regs_permutation = plcrash_async_cfe_register_encode(encoded_regs, encoded_regs_count);
uint32_t encoding = UNWIND_X86_64_MODE_STACK_IMMD |
INSERT_BITS(encoded_stack_size/8, UNWIND_X86_64_FRAMELESS_STACK_SIZE) |
INSERT_BITS(encoded_regs_count, UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT) |
INSERT_BITS(encoded_regs_permutation, UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION);
STAssertEquals(plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding), PLCRASH_ESUCCESS, @"Failed to decode entry");
/* Initialize default thread state */
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86_64), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
plcrash_async_thread_state_set_reg(&ts, PLCRASH_REG_SP, (plcrash_greg_t) &stackframe);
/* Apply */
plcrash_async_thread_state_t nts;
plcrash_error_t err = plcrash_async_cfe_entry_apply(mach_task_self(), 0x0, &ts, &entry, &nts);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to apply state to thread");
/* Verify */
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RSP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RIP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RBP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R12), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R13), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R14), @"Missing expected register");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RSP), (plcrash_greg_t)&stackframe[5], @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RIP), (plcrash_greg_t)2, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RBP), (plcrash_greg_t)10, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R12), (plcrash_greg_t)12, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R13), (plcrash_greg_t)13, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R14), (plcrash_greg_t)14, @"Incorrect register value");
plcrash_async_cfe_entry_free(&entry);
}
/**
* Apply an x86-64 indirect 'frameless' encoding.
*/
- (void) testX86_64_ApplyFramePTRState_IND {
plcrash_async_cfe_entry_t entry;
plcrash_async_thread_state_t ts;
/* Set up a faux frame */
uint64_t stackframe[] = {
10, // rbp
12, // r12
13, // r13
14, // r14
2, // ret addr
};
/* Create a frame encoding */
const uint32_t encoded_stack_size = 128;
const uint32_t encoded_regs[] = { UNWIND_X86_64_REG_RBP, UNWIND_X86_64_REG_R12, UNWIND_X86_64_REG_R13, UNWIND_X86_64_REG_R14 };
const uint32_t encoded_regs_count = sizeof(encoded_regs) / sizeof(encoded_regs[0]);
const uint32_t encoded_regs_permutation = plcrash_async_cfe_register_encode(encoded_regs, encoded_regs_count);
/* Indirect address target */
uint32_t indirect_encoded_stack_size = 32;
uint32_t encoded_stack_adjust = 8;
pl_vm_address_t function_address = ((pl_vm_address_t) &indirect_encoded_stack_size) - encoded_stack_size;
uint32_t encoding = UNWIND_X86_64_MODE_STACK_IND |
INSERT_BITS(encoded_stack_size, UNWIND_X86_64_FRAMELESS_STACK_SIZE) |
INSERT_BITS(encoded_regs_count, UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT) |
INSERT_BITS(encoded_regs_permutation, UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION) |
INSERT_BITS(encoded_stack_adjust/8, UNWIND_X86_64_FRAMELESS_STACK_ADJUST);
STAssertEquals(plcrash_async_cfe_entry_init(&entry, CPU_TYPE_X86_64, encoding), PLCRASH_ESUCCESS, @"Failed to decode entry");
/* Initialize default thread state */
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86_64), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
plcrash_async_thread_state_set_reg(&ts, PLCRASH_REG_SP, (plcrash_greg_t) &stackframe);
/* Apply */
plcrash_async_thread_state_t nts;
plcrash_error_t err = plcrash_async_cfe_entry_apply(mach_task_self(), function_address, &ts, &entry, &nts);
STAssertEquals(err, PLCRASH_ESUCCESS, @"Failed to apply state to thread");
/* Verify */
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RSP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RIP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_RBP), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R12), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R13), @"Missing expected register");
STAssertTrue(plcrash_async_thread_state_has_reg(&nts, PLCRASH_X86_64_R14), @"Missing expected register");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RSP), (plcrash_greg_t)&stackframe[5], @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RIP), (plcrash_greg_t)2, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_RBP), (plcrash_greg_t)10, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R12), (plcrash_greg_t)12, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R13), (plcrash_greg_t)13, @"Incorrect register value");
STAssertEquals(plcrash_async_thread_state_get_reg(&nts, PLCRASH_X86_64_R14), (plcrash_greg_t)14, @"Incorrect register value");
plcrash_async_cfe_entry_free(&entry);
}
#endif /* PLCRASH_ASYNC_THREAD_X86_SUPPORT */
@end
#endif /* PLCRASH_FEATURE_UNWIND_COMPACT */