diff options
Diffstat (limited to 'src/common/linux')
32 files changed, 2591 insertions, 410 deletions
diff --git a/src/common/linux/breakpad_getcontext.S b/src/common/linux/breakpad_getcontext.S new file mode 100644 index 00000000..fea0109d --- /dev/null +++ b/src/common/linux/breakpad_getcontext.S @@ -0,0 +1,486 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// A minimalistic implementation of getcontext() to be used by +// Google Breakpad when getcontext() is not available in libc. + +#include "common/linux/ucontext_constants.h" + +/* int getcontext (ucontext_t *ucp) */ + +#if defined(__arm__) + + .text + .global breakpad_getcontext + .hidden breakpad_getcontext + .type breakpad_getcontext, #function + .align 0 + .fnstart +breakpad_getcontext: + + /* First, save r4-r11 */ + add r1, r0, #(MCONTEXT_GREGS_OFFSET + 4*4) + stm r1, {r4-r11} + + /* r12 is a scratch register, don't save it */ + + /* Save sp and lr explicitly. */ + /* - sp can't be stored with stmia in Thumb-2 */ + /* - STM instructions that store sp and pc are deprecated in ARM */ + str sp, [r0, #(MCONTEXT_GREGS_OFFSET + 13*4)] + str lr, [r0, #(MCONTEXT_GREGS_OFFSET + 14*4)] + + /* Save the caller's address in 'pc' */ + str lr, [r0, #(MCONTEXT_GREGS_OFFSET + 15*4)] + + /* Save ucontext_t* pointer across next call */ + mov r4, r0 + + /* Call sigprocmask(SIG_BLOCK, NULL, &(ucontext->uc_sigmask)) */ + mov r0, #0 /* SIG_BLOCK */ + mov r1, #0 /* NULL */ + add r2, r4, #UCONTEXT_SIGMASK_OFFSET + bl sigprocmask(PLT) + + /* Intentionally do not save the FPU state here. This is because on + * Linux/ARM, one should instead use ptrace(PTRACE_GETFPREGS) or + * ptrace(PTRACE_GETVFPREGS) to get it. + * + * Note that a real implementation of getcontext() would need to save + * this here to allow setcontext()/swapcontext() to work correctly. + */ + + /* Restore the values of r4 and lr */ + mov r0, r4 + ldr lr, [r0, #(MCONTEXT_GREGS_OFFSET + 14*4)] + ldr r4, [r0, #(MCONTEXT_GREGS_OFFSET + 4*4)] + + /* Return 0 */ + mov r0, #0 + bx lr + + .fnend + .size breakpad_getcontext, . - breakpad_getcontext + +#elif defined(__aarch64__) + +#define _NSIG 64 +#define __NR_rt_sigprocmask 135 + + .text + .global breakpad_getcontext + .hidden breakpad_getcontext + .type breakpad_getcontext, #function + .align 4 + .cfi_startproc +breakpad_getcontext: + + /* The saved context will return to the getcontext() call point + with a return value of 0 */ + str xzr, [x0, MCONTEXT_GREGS_OFFSET + 0 * REGISTER_SIZE] + + stp x18, x19, [x0, MCONTEXT_GREGS_OFFSET + 18 * REGISTER_SIZE] + stp x20, x21, [x0, MCONTEXT_GREGS_OFFSET + 20 * REGISTER_SIZE] + stp x22, x23, [x0, MCONTEXT_GREGS_OFFSET + 22 * REGISTER_SIZE] + stp x24, x25, [x0, MCONTEXT_GREGS_OFFSET + 24 * REGISTER_SIZE] + stp x26, x27, [x0, MCONTEXT_GREGS_OFFSET + 26 * REGISTER_SIZE] + stp x28, x29, [x0, MCONTEXT_GREGS_OFFSET + 28 * REGISTER_SIZE] + str x30, [x0, MCONTEXT_GREGS_OFFSET + 30 * REGISTER_SIZE] + + /* Place LR into the saved PC, this will ensure that when + switching to this saved context with setcontext() control + will pass back to the caller of getcontext(), we have + already arranged to return the appropriate return value in x0 + above. */ + str x30, [x0, MCONTEXT_PC_OFFSET] + + /* Save the current SP */ + mov x2, sp + str x2, [x0, MCONTEXT_SP_OFFSET] + + /* Initialize the pstate. */ + str xzr, [x0, MCONTEXT_PSTATE_OFFSET] + + /* Figure out where to place the first context extension + block. */ + add x2, x0, #MCONTEXT_EXTENSION_OFFSET + + /* Write the context extension fpsimd header. */ + mov w3, #(FPSIMD_MAGIC & 0xffff) + movk w3, #(FPSIMD_MAGIC >> 16), lsl #16 + str w3, [x2, #FPSIMD_CONTEXT_MAGIC_OFFSET] + mov w3, #FPSIMD_CONTEXT_SIZE + str w3, [x2, #FPSIMD_CONTEXT_SIZE_OFFSET] + + /* Fill in the FP SIMD context. */ + add x3, x2, #(FPSIMD_CONTEXT_VREGS_OFFSET + 8 * SIMD_REGISTER_SIZE) + stp d8, d9, [x3], #(2 * SIMD_REGISTER_SIZE) + stp d10, d11, [x3], #(2 * SIMD_REGISTER_SIZE) + stp d12, d13, [x3], #(2 * SIMD_REGISTER_SIZE) + stp d14, d15, [x3], #(2 * SIMD_REGISTER_SIZE) + + add x3, x2, FPSIMD_CONTEXT_FPSR_OFFSET + + mrs x4, fpsr + str w4, [x3] + + mrs x4, fpcr + str w4, [x3, FPSIMD_CONTEXT_FPCR_OFFSET - FPSIMD_CONTEXT_FPSR_OFFSET] + + /* Write the termination context extension header. */ + add x2, x2, #FPSIMD_CONTEXT_SIZE + + str xzr, [x2, #FPSIMD_CONTEXT_MAGIC_OFFSET] + str xzr, [x2, #FPSIMD_CONTEXT_SIZE_OFFSET] + + /* Grab the signal mask */ + /* rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) */ + add x2, x0, #UCONTEXT_SIGMASK_OFFSET + mov x0, #0 /* SIG_BLOCK */ + mov x1, #0 /* NULL */ + mov x3, #(_NSIG / 8) + mov x8, #__NR_rt_sigprocmask + svc 0 + + /* Return x0 for success */ + mov x0, 0 + ret + + .cfi_endproc + .size breakpad_getcontext, . - breakpad_getcontext + +#elif defined(__i386__) + + .text + .global breakpad_getcontext + .hidden breakpad_getcontext + .align 4 + .type breakpad_getcontext, @function + +breakpad_getcontext: + + movl 4(%esp), %eax /* eax = uc */ + + /* Save register values */ + movl %ecx, MCONTEXT_ECX_OFFSET(%eax) + movl %edx, MCONTEXT_EDX_OFFSET(%eax) + movl %ebx, MCONTEXT_EBX_OFFSET(%eax) + movl %edi, MCONTEXT_EDI_OFFSET(%eax) + movl %esi, MCONTEXT_ESI_OFFSET(%eax) + movl %ebp, MCONTEXT_EBP_OFFSET(%eax) + + movl (%esp), %edx /* return address */ + lea 4(%esp), %ecx /* exclude return address from stack */ + mov %edx, MCONTEXT_EIP_OFFSET(%eax) + mov %ecx, MCONTEXT_ESP_OFFSET(%eax) + + xorl %ecx, %ecx + movw %fs, %cx + mov %ecx, MCONTEXT_FS_OFFSET(%eax) + + movl $0, MCONTEXT_EAX_OFFSET(%eax) + + /* Save floating point state to fpregstate, then update + * the fpregs pointer to point to it */ + leal UCONTEXT_FPREGS_MEM_OFFSET(%eax), %ecx + fnstenv (%ecx) + fldenv (%ecx) + mov %ecx, UCONTEXT_FPREGS_OFFSET(%eax) + + /* Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) */ + leal UCONTEXT_SIGMASK_OFFSET(%eax), %edx + xorl %ecx, %ecx + push %edx /* &uc->uc_sigmask */ + push %ecx /* NULL */ + push %ecx /* SIGBLOCK == 0 on i386 */ + call sigprocmask@PLT + addl $12, %esp + + movl $0, %eax + ret + + .size breakpad_getcontext, . - breakpad_getcontext + +#elif defined(__mips__) + +// This implementation is inspired by implementation of getcontext in glibc. +#include <asm-mips/asm.h> +#include <asm-mips/regdef.h> +#if _MIPS_SIM == _ABIO32 +#include <asm-mips/fpregdef.h> +#endif + +// from asm-mips/asm.h +#if _MIPS_SIM == _ABIO32 +#define ALSZ 7 +#define ALMASK ~7 +#define SZREG 4 +#else // _MIPS_SIM != _ABIO32 +#define ALSZ 15 +#define ALMASK ~15 +#define SZREG 8 +#endif + +#include <asm/unistd.h> // for __NR_rt_sigprocmask + +#define _NSIG8 128 / 8 +#define SIG_BLOCK 1 + + + .text +LOCALS_NUM = 1 // save gp on stack +FRAME_SIZE = ((LOCALS_NUM * SZREG) + ALSZ) & ALMASK + +GP_FRAME_OFFSET = FRAME_SIZE - (1 * SZREG) +MCONTEXT_REG_SIZE = 8 + +#if _MIPS_SIM == _ABIO32 + +NESTED (breakpad_getcontext, FRAME_SIZE, ra) + .mask 0x00000000, 0 + .fmask 0x00000000, 0 + + .set noreorder + .cpload t9 + .set reorder + + move a2, sp +#define _SP a2 + + addiu sp, -FRAME_SIZE + .cprestore GP_FRAME_OFFSET + + sw s0, (16 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw s1, (17 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw s2, (18 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw s3, (19 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw s4, (20 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw s5, (21 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw s6, (22 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw s7, (23 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw _SP, (29 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw fp, (30 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw ra, (31 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sw ra, MCONTEXT_PC_OFFSET(a0) + +#ifdef __mips_hard_float + s.d fs0, (20 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d fs1, (22 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d fs2, (24 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d fs3, (26 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d fs4, (28 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d fs5, (30 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + + cfc1 v1, fcr31 + sw v1, MCONTEXT_FPC_CSR(a0) +#endif // __mips_hard_float + + /* rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) */ + li a3, _NSIG8 + addu a2, a0, UCONTEXT_SIGMASK_OFFSET + move a1, zero + li a0, SIG_BLOCK + li v0, __NR_rt_sigprocmask + syscall + + addiu sp, FRAME_SIZE + jr ra + +END (breakpad_getcontext) +#else + +#ifndef NESTED +/* + * NESTED - declare nested routine entry point + */ +#define NESTED(symbol, framesize, rpc) \ + .globl symbol; \ + .align 2; \ + .type symbol,@function; \ + .ent symbol,0; \ +symbol: .frame sp, framesize, rpc; +#endif + +/* + * END - mark end of function + */ +#ifndef END +# define END(function) \ + .end function; \ + .size function,.-function +#endif + +/* int getcontext (ucontext_t *ucp) */ + +NESTED (breakpad_getcontext, FRAME_SIZE, ra) + .mask 0x10000000, 0 + .fmask 0x00000000, 0 + + move a2, sp +#define _SP a2 + move a3, gp +#define _GP a3 + + daddiu sp, -FRAME_SIZE + .cpsetup $25, GP_FRAME_OFFSET, breakpad_getcontext + + /* Store a magic flag. */ + li v1, 1 + sd v1, (0 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) /* zero */ + + sd s0, (16 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s1, (17 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s2, (18 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s3, (19 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s4, (20 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s5, (21 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s6, (22 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s7, (23 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd _GP, (28 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd _SP, (29 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd s8, (30 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd ra, (31 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)(a0) + sd ra, MCONTEXT_PC_OFFSET(a0) + +#ifdef __mips_hard_float + s.d $f24, (24 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d $f25, (25 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d $f26, (26 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d $f27, (27 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d $f28, (28 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d $f29, (29 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d $f30, (30 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + s.d $f31, (31 * MCONTEXT_REG_SIZE + MCONTEXT_FPREGS_OFFSET)(a0) + + cfc1 v1, $31 + sw v1, MCONTEXT_FPC_CSR(a0) +#endif /* __mips_hard_float */ + +/* rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) */ + li a3, _NSIG8 + daddu a2, a0, UCONTEXT_SIGMASK_OFFSET + move a1, zero + li a0, SIG_BLOCK + + li v0, __NR_rt_sigprocmask + syscall + + .cpreturn + daddiu sp, FRAME_SIZE + move v0, zero + jr ra + +END (breakpad_getcontext) +#endif // _MIPS_SIM == _ABIO32 + +#elif defined(__x86_64__) +/* The x64 implementation of breakpad_getcontext was derived in part + from the implementation of libunwind which requires the following + notice. */ +/* libunwind - a platform-independent unwind library + Copyright (C) 2008 Google, Inc + Contributed by Paul Pluzhnikov <ppluzhnikov@google.com> + Copyright (C) 2010 Konstantin Belousov <kib@freebsd.org> + +This file is part of libunwind. + +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. */ + + .text + .global breakpad_getcontext + .hidden breakpad_getcontext + .align 4 + .type breakpad_getcontext, @function + +breakpad_getcontext: + .cfi_startproc + + /* Callee saved: RBX, RBP, R12-R15 */ + movq %r12, MCONTEXT_GREGS_R12(%rdi) + movq %r13, MCONTEXT_GREGS_R13(%rdi) + movq %r14, MCONTEXT_GREGS_R14(%rdi) + movq %r15, MCONTEXT_GREGS_R15(%rdi) + movq %rbp, MCONTEXT_GREGS_RBP(%rdi) + movq %rbx, MCONTEXT_GREGS_RBX(%rdi) + + /* Save argument registers (not strictly needed, but setcontext + restores them, so don't restore garbage). */ + movq %r8, MCONTEXT_GREGS_R8(%rdi) + movq %r9, MCONTEXT_GREGS_R9(%rdi) + movq %rdi, MCONTEXT_GREGS_RDI(%rdi) + movq %rsi, MCONTEXT_GREGS_RSI(%rdi) + movq %rdx, MCONTEXT_GREGS_RDX(%rdi) + movq %rax, MCONTEXT_GREGS_RAX(%rdi) + movq %rcx, MCONTEXT_GREGS_RCX(%rdi) + + /* Save fp state (not needed, except for setcontext not + restoring garbage). */ + leaq MCONTEXT_FPREGS_MEM(%rdi),%r8 + movq %r8, MCONTEXT_FPREGS_PTR(%rdi) + fnstenv (%r8) + stmxcsr FPREGS_OFFSET_MXCSR(%r8) + + leaq 8(%rsp), %rax /* exclude this call. */ + movq %rax, MCONTEXT_GREGS_RSP(%rdi) + + movq 0(%rsp), %rax + movq %rax, MCONTEXT_GREGS_RIP(%rdi) + + /* Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) */ + leaq UCONTEXT_SIGMASK_OFFSET(%rdi), %rdx // arg3 + xorq %rsi, %rsi // arg2 NULL + xorq %rdi, %rdi // arg1 SIGBLOCK == 0 + call sigprocmask@PLT + + /* Always return 0 for success, even if sigprocmask failed. */ + xorl %eax, %eax + ret + .cfi_endproc + .size breakpad_getcontext, . - breakpad_getcontext + +#else +#error "This file has not been ported for your CPU!" +#endif diff --git a/src/common/linux/breakpad_getcontext.h b/src/common/linux/breakpad_getcontext.h new file mode 100644 index 00000000..1418cde6 --- /dev/null +++ b/src/common/linux/breakpad_getcontext.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_BREAKPAD_COMMON_LINUX_INCLUDE_UCONTEXT_H +#define GOOGLE_BREAKPAD_COMMON_LINUX_INCLUDE_UCONTEXT_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifndef HAVE_GETCONTEXT + +#include <signal.h> + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Provided by src/common/linux/breakpad_getcontext.S +int breakpad_getcontext(ucontext_t* ucp); + +#define getcontext(x) breakpad_getcontext(x) + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // HAVE_GETCONTEXT + +#endif // GOOGLE_BREAKPAD_COMMON_LINUX_INCLUDE_UCONTEXT_H diff --git a/src/common/linux/breakpad_getcontext_unittest.cc b/src/common/linux/breakpad_getcontext_unittest.cc new file mode 100644 index 00000000..a57bfedf --- /dev/null +++ b/src/common/linux/breakpad_getcontext_unittest.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// asm/sigcontext.h can't be included with signal.h on glibc or +// musl, so only compare _libc_fpstate and _fpstate on Android. +#if defined(__ANDROID__) && defined(__x86_64__) +#include <asm/sigcontext.h> +#endif + +#include <sys/ucontext.h> + +#include <type_traits> + +#include "breakpad_googletest_includes.h" +#include "common/linux/ucontext_constants.h" + +template <int left, int right> +struct CompileAssertEquals { + // a compilation error here indicates left and right are not equal. + char left_too_large[right - left]; + // a compilation error here indicates left and right are not equal. + char right_too_large[left - right]; +}; + +#define COMPILE_ASSERT_EQ(left, right, tag) \ + CompileAssertEquals<left, right> tag; + +TEST(AndroidUContext, GRegsOffset) { +#if defined(__arm__) + // There is no gregs[] array on ARM, so compare to the offset of + // first register fields, since they're stored in order. + ASSERT_EQ(static_cast<size_t>(MCONTEXT_GREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.arm_r0)); +#elif defined(__aarch64__) + // There is no gregs[] array on ARM, so compare to the offset of + // first register fields, since they're stored in order. + ASSERT_EQ(static_cast<size_t>(MCONTEXT_GREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.regs[0])); + ASSERT_EQ(static_cast<size_t>(MCONTEXT_SP_OFFSET), + offsetof(ucontext_t,uc_mcontext.sp)); + ASSERT_EQ(static_cast<size_t>(MCONTEXT_PC_OFFSET), + offsetof(ucontext_t,uc_mcontext.pc)); + ASSERT_EQ(static_cast<size_t>(MCONTEXT_PSTATE_OFFSET), + offsetof(ucontext_t,uc_mcontext.pstate)); + ASSERT_EQ(static_cast<size_t>(MCONTEXT_EXTENSION_OFFSET), + offsetof(ucontext_t,uc_mcontext.__reserved)); +#elif defined(__i386__) + ASSERT_EQ(static_cast<size_t>(MCONTEXT_GREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.gregs)); +#define CHECK_REG(x) \ + ASSERT_EQ(static_cast<size_t>(MCONTEXT_##x##_OFFSET), \ + offsetof(ucontext_t,uc_mcontext.gregs[REG_##x])) + CHECK_REG(GS); + CHECK_REG(FS); + CHECK_REG(ES); + CHECK_REG(DS); + CHECK_REG(EDI); + CHECK_REG(ESI); + CHECK_REG(EBP); + CHECK_REG(ESP); + CHECK_REG(EBX); + CHECK_REG(EDX); + CHECK_REG(ECX); + CHECK_REG(EAX); + CHECK_REG(TRAPNO); + CHECK_REG(ERR); + CHECK_REG(EIP); + CHECK_REG(CS); + CHECK_REG(EFL); + CHECK_REG(UESP); + CHECK_REG(SS); + + ASSERT_EQ(static_cast<size_t>(UCONTEXT_FPREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.fpregs)); + + ASSERT_EQ(static_cast<size_t>(UCONTEXT_FPREGS_MEM_OFFSET), + offsetof(ucontext_t,__fpregs_mem)); +#elif defined(__mips__) + ASSERT_EQ(static_cast<size_t>(MCONTEXT_GREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.gregs)); + + // PC for mips is not part of gregs. + ASSERT_EQ(static_cast<size_t>(MCONTEXT_PC_OFFSET), + offsetof(ucontext_t,uc_mcontext.pc)); + + ASSERT_EQ(static_cast<size_t>(MCONTEXT_FPREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.fpregs)); + + ASSERT_EQ(static_cast<size_t>(MCONTEXT_FPC_CSR), + offsetof(ucontext_t,uc_mcontext.fpc_csr)); +#elif defined(__x86_64__) + + COMPILE_ASSERT_EQ(static_cast<size_t>(MCONTEXT_GREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.gregs), + mcontext_gregs_offset); +#define CHECK_REG(x) \ + COMPILE_ASSERT_EQ(static_cast<size_t>(MCONTEXT_GREGS_##x), \ + offsetof(ucontext_t,uc_mcontext.gregs[REG_##x]), reg_##x) + CHECK_REG(R8); + CHECK_REG(R9); + CHECK_REG(R10); + CHECK_REG(R11); + CHECK_REG(R12); + CHECK_REG(R13); + CHECK_REG(R14); + CHECK_REG(R15); + CHECK_REG(RDI); + CHECK_REG(RSI); + CHECK_REG(RBP); + CHECK_REG(RBX); + CHECK_REG(RDX); + CHECK_REG(RAX); + CHECK_REG(RCX); + CHECK_REG(RSP); + CHECK_REG(RIP); + + // sigcontext is an analog to mcontext_t. The layout should be the same. + COMPILE_ASSERT_EQ(offsetof(mcontext_t,fpregs), + offsetof(sigcontext,fpstate), sigcontext_fpstate); + +#if defined(__ANDROID__) + // Check that _fpstate from asm/sigcontext.h is essentially the same + // as _libc_fpstate. + COMPILE_ASSERT_EQ(sizeof(_libc_fpstate), sizeof(_fpstate), + sigcontext_fpstate_size); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,cwd),offsetof(_fpstate,cwd), + sigcontext_fpstate_cwd); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,swd),offsetof(_fpstate,swd), + sigcontext_fpstate_swd); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,ftw),offsetof(_fpstate,twd), + sigcontext_fpstate_twd); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,fop),offsetof(_fpstate,fop), + sigcontext_fpstate_fop); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,rip),offsetof(_fpstate,rip), + sigcontext_fpstate_rip); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,rdp),offsetof(_fpstate,rdp), + sigcontext_fpstate_rdp); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,mxcsr),offsetof(_fpstate,mxcsr), + sigcontext_fpstate_mxcsr); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,mxcr_mask), + offsetof(_fpstate,mxcsr_mask), + sigcontext_fpstate_mxcsr_mask); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,_st), offsetof(_fpstate,st_space), + sigcontext_fpstate_stspace); + COMPILE_ASSERT_EQ(offsetof(_libc_fpstate,_xmm), offsetof(_fpstate,xmm_space), + sigcontext_fpstate_xmm_space); +#endif + + COMPILE_ASSERT_EQ(MCONTEXT_FPREGS_PTR, + offsetof(ucontext_t,uc_mcontext.fpregs), + mcontext_fpregs_ptr); + COMPILE_ASSERT_EQ(MCONTEXT_FPREGS_MEM, offsetof(ucontext_t,__fpregs_mem), + mcontext_fpregs_mem); + COMPILE_ASSERT_EQ(FPREGS_OFFSET_MXCSR, + offsetof(std::remove_pointer<fpregset_t>::type,mxcsr), + fpregs_offset_mxcsr); + COMPILE_ASSERT_EQ(UCONTEXT_SIGMASK_OFFSET, offsetof(ucontext_t, uc_sigmask), + ucontext_sigmask); +#else + ASSERT_EQ(static_cast<size_t>(MCONTEXT_GREGS_OFFSET), + offsetof(ucontext_t,uc_mcontext.gregs)); +#endif +} + +TEST(AndroidUContext, SigmakOffset) { + ASSERT_EQ(static_cast<size_t>(UCONTEXT_SIGMASK_OFFSET), + offsetof(ucontext_t,uc_sigmask)); +} diff --git a/src/common/linux/dump_symbols.cc b/src/common/linux/dump_symbols.cc index 0bcc18ab..b7e77ab7 100644 --- a/src/common/linux/dump_symbols.cc +++ b/src/common/linux/dump_symbols.cc @@ -38,7 +38,9 @@ #include <elf.h> #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <link.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -57,13 +59,16 @@ #include "common/dwarf_cfi_to_module.h" #include "common/dwarf_cu_to_module.h" #include "common/dwarf_line_to_module.h" +#include "common/dwarf_range_list_handler.h" #include "common/linux/crc32.h" #include "common/linux/eintr_wrapper.h" #include "common/linux/elfutils.h" #include "common/linux/elfutils-inl.h" #include "common/linux/elf_symbols_to_module.h" #include "common/linux/file_id.h" +#include "common/memory_allocator.h" #include "common/module.h" +#include "common/path_helper.h" #include "common/scoped_ptr.h" #ifndef NO_STABS_SUPPORT #include "common/stabs_reader.h" @@ -78,17 +83,22 @@ using google_breakpad::DumpOptions; using google_breakpad::DwarfCFIToModule; using google_breakpad::DwarfCUToModule; using google_breakpad::DwarfLineToModule; +using google_breakpad::DwarfRangeListHandler; using google_breakpad::ElfClass; using google_breakpad::ElfClass32; using google_breakpad::ElfClass64; +using google_breakpad::FileID; using google_breakpad::FindElfSectionByName; using google_breakpad::GetOffset; using google_breakpad::IsValidElf; +using google_breakpad::kDefaultBuildIdSize; using google_breakpad::Module; +using google_breakpad::PageAllocator; #ifndef NO_STABS_SUPPORT using google_breakpad::StabsToModule; #endif using google_breakpad::scoped_ptr; +using google_breakpad::wasteful_vector; // Define AARCH64 ELF architecture if host machine does not include this define. #ifndef EM_AARCH64 @@ -172,6 +182,23 @@ typename ElfClass::Addr GetLoadingAddress( return 0; } +// Find the set of address ranges for all PT_LOAD segments. +template <typename ElfClass> +vector<Module::Range> GetPtLoadSegmentRanges( + const typename ElfClass::Phdr* program_headers, + int nheader) { + typedef typename ElfClass::Phdr Phdr; + vector<Module::Range> ranges; + + for (int i = 0; i < nheader; ++i) { + const Phdr& header = program_headers[i]; + if (header.p_type == PT_LOAD) { + ranges.push_back(Module::Range(header.p_vaddr, header.p_memsz)); + } + } + return ranges; +} + #ifndef NO_STABS_SUPPORT template<typename ElfClass> bool LoadStabs(const typename ElfClass::Ehdr* elf_header, @@ -200,6 +227,30 @@ bool LoadStabs(const typename ElfClass::Ehdr* elf_header, } #endif // NO_STABS_SUPPORT +// A range handler that accepts rangelist data parsed by +// dwarf2reader::RangeListReader and populates a range vector (typically +// owned by a function) with the results. +class DumperRangesHandler : public DwarfCUToModule::RangesHandler { + public: + DumperRangesHandler(const uint8_t *buffer, uint64_t size, + dwarf2reader::ByteReader* reader) + : buffer_(buffer), size_(size), reader_(reader) { } + + bool ReadRanges(uint64_t offset, Module::Address base_address, + vector<Module::Range>* ranges) { + DwarfRangeListHandler handler(base_address, ranges); + dwarf2reader::RangeListReader rangelist_reader(buffer_, size_, reader_, + &handler); + + return rangelist_reader.ReadRangeList(offset); + } + + private: + const uint8_t *buffer_; + uint64_t size_; + dwarf2reader::ByteReader* reader_; +}; + // A line-to-module loader that accepts line number info parsed by // dwarf2reader::LineInfo and populates a Module and a line vector // with the results. @@ -211,7 +262,7 @@ class DumperLineToModule: public DwarfCUToModule::LineToModuleHandler { void StartCompilationUnit(const string& compilation_dir) { compilation_dir_ = compilation_dir; } - void ReadProgram(const char* program, uint64 length, + void ReadProgram(const uint8_t *program, uint64_t length, Module* module, std::vector<Module::Line>* lines) { DwarfLineToModule handler(module, compilation_dir_, lines); dwarf2reader::LineInfo parser(program, length, byte_reader_, &handler); @@ -249,31 +300,45 @@ bool LoadDwarf(const string& dwarf_filename, string name = GetOffset<ElfClass, char>(elf_header, section_names->sh_offset) + section->sh_name; - const char* contents = GetOffset<ElfClass, char>(elf_header, - section->sh_offset); + const uint8_t *contents = GetOffset<ElfClass, uint8_t>(elf_header, + section->sh_offset); file_context.AddSectionToSectionMap(name, contents, section->sh_size); } + // Optional .debug_ranges reader + scoped_ptr<DumperRangesHandler> ranges_handler; + dwarf2reader::SectionMap::const_iterator ranges_entry = + file_context.section_map().find(".debug_ranges"); + if (ranges_entry != file_context.section_map().end()) { + const std::pair<const uint8_t *, uint64_t>& ranges_section = + ranges_entry->second; + ranges_handler.reset( + new DumperRangesHandler(ranges_section.first, ranges_section.second, + &byte_reader)); + } + // Parse all the compilation units in the .debug_info section. DumperLineToModule line_to_module(&byte_reader); dwarf2reader::SectionMap::const_iterator debug_info_entry = file_context.section_map().find(".debug_info"); assert(debug_info_entry != file_context.section_map().end()); - const std::pair<const char*, uint64>& debug_info_section = + const std::pair<const uint8_t *, uint64_t>& debug_info_section = debug_info_entry->second; // This should never have been called if the file doesn't have a // .debug_info section. assert(debug_info_section.first); - uint64 debug_info_length = debug_info_section.second; - for (uint64 offset = 0; offset < debug_info_length;) { + uint64_t debug_info_length = debug_info_section.second; + for (uint64_t offset = 0; offset < debug_info_length;) { // Make a handler for the root DIE that populates MODULE with the // data that was found. DwarfCUToModule::WarningReporter reporter(dwarf_filename, offset); - DwarfCUToModule root_handler(&file_context, &line_to_module, &reporter); + DwarfCUToModule root_handler(&file_context, &line_to_module, + ranges_handler.get(), &reporter); // Make a Dwarf2Handler that drives the DIEHandler. dwarf2reader::DIEDispatcher die_dispatcher(&root_handler); // Make a DWARF parser for the compilation unit at OFFSET. - dwarf2reader::CompilationUnit reader(file_context.section_map(), + dwarf2reader::CompilationUnit reader(dwarf_filename, + file_context.section_map(), offset, &byte_reader, &die_dispatcher); @@ -336,8 +401,8 @@ bool LoadDwarfCFI(const string& dwarf_filename, dwarf2reader::ENDIANNESS_BIG : dwarf2reader::ENDIANNESS_LITTLE; // Find the call frame information and its size. - const char* cfi = - GetOffset<ElfClass, char>(elf_header, section->sh_offset); + const uint8_t *cfi = + GetOffset<ElfClass, uint8_t>(elf_header, section->sh_offset); size_t cfi_size = section->sh_size; // Plug together the parser, handler, and their entourages. @@ -424,12 +489,13 @@ bool IsSameFile(const char* left_abspath, const string& right_path) { // Read the .gnu_debuglink and get the debug file name. If anything goes // wrong, return an empty string. -string ReadDebugLink(const char* debuglink, +string ReadDebugLink(const uint8_t *debuglink, const size_t debuglink_size, const bool big_endian, const string& obj_file, const std::vector<string>& debug_dirs) { - size_t debuglink_len = strlen(debuglink) + 5; // Include '\0' + CRC32. + // Include '\0' + CRC32 (4 bytes). + size_t debuglink_len = strlen(reinterpret_cast<const char *>(debuglink)) + 5; debuglink_len = 4 * ((debuglink_len + 3) / 4); // Round up to 4 bytes. // Sanity check. @@ -450,7 +516,8 @@ string ReadDebugLink(const char* debuglink, std::vector<string>::const_iterator it; for (it = debug_dirs.begin(); it < debug_dirs.end(); ++it) { const string& debug_dir = *it; - debuglink_path = debug_dir + "/" + debuglink; + debuglink_path = debug_dir + "/" + + reinterpret_cast<const char *>(debuglink); // There is the annoying case of /path/to/foo.so having foo.so as the // debug link file name. Thus this may end up opening /path/to/foo.so again, @@ -592,7 +659,6 @@ bool LoadSymbols(const string& obj_file, typedef typename ElfClass::Addr Addr; typedef typename ElfClass::Phdr Phdr; typedef typename ElfClass::Shdr Shdr; - typedef typename ElfClass::Word Word; Addr loading_addr = GetLoadingAddress<ElfClass>( GetOffset<ElfClass, Phdr>(elf_header, elf_header->e_phoff), @@ -600,8 +666,14 @@ bool LoadSymbols(const string& obj_file, module->SetLoadAddress(loading_addr); info->set_loading_addr(loading_addr, obj_file); - Word debug_section_type = - elf_header->e_machine == EM_MIPS ? SHT_MIPS_DWARF : SHT_PROGBITS; + // Allow filtering of extraneous debug information in partitioned libraries. + // Such libraries contain debug information for all libraries extracted from + // the same combined library, implying extensive duplication. + vector<Module::Range> address_ranges = GetPtLoadSegmentRanges<ElfClass>( + GetOffset<ElfClass, Phdr>(elf_header, elf_header->e_phoff), + elf_header->e_phnum); + module->SetAddressRanges(address_ranges); + const Shdr* sections = GetOffset<ElfClass, Shdr>(elf_header, elf_header->e_shoff); const Shdr* section_names = sections + elf_header->e_shstrndx; @@ -635,9 +707,19 @@ bool LoadSymbols(const string& obj_file, // Look for DWARF debugging information, and load it if present. const Shdr* dwarf_section = - FindElfSectionByName<ElfClass>(".debug_info", debug_section_type, + FindElfSectionByName<ElfClass>(".debug_info", SHT_PROGBITS, sections, names, names_end, elf_header->e_shnum); + + // .debug_info section type is SHT_PROGBITS for mips on pnacl toolchains, + // but MIPS_DWARF for regular gnu toolchains, so both need to be checked + if (elf_header->e_machine == EM_MIPS && !dwarf_section) { + dwarf_section = + FindElfSectionByName<ElfClass>(".debug_info", SHT_MIPS_DWARF, + sections, names, names_end, + elf_header->e_shnum); + } + if (dwarf_section) { found_debug_info_section = true; found_usable_info = true; @@ -650,32 +732,61 @@ bool LoadSymbols(const string& obj_file, } // See if there are export symbols available. - const Shdr* dynsym_section = - FindElfSectionByName<ElfClass>(".dynsym", SHT_DYNSYM, - sections, names, names_end, - elf_header->e_shnum); - const Shdr* dynstr_section = - FindElfSectionByName<ElfClass>(".dynstr", SHT_STRTAB, - sections, names, names_end, - elf_header->e_shnum); - if (dynsym_section && dynstr_section) { - info->LoadedSection(".dynsym"); + const Shdr* symtab_section = + FindElfSectionByName<ElfClass>(".symtab", SHT_SYMTAB, + sections, names, names_end, + elf_header->e_shnum); + const Shdr* strtab_section = + FindElfSectionByName<ElfClass>(".strtab", SHT_STRTAB, + sections, names, names_end, + elf_header->e_shnum); + if (symtab_section && strtab_section) { + info->LoadedSection(".symtab"); - const uint8_t* dynsyms = + const uint8_t* symtab = GetOffset<ElfClass, uint8_t>(elf_header, - dynsym_section->sh_offset); - const uint8_t* dynstrs = + symtab_section->sh_offset); + const uint8_t* strtab = GetOffset<ElfClass, uint8_t>(elf_header, - dynstr_section->sh_offset); + strtab_section->sh_offset); bool result = - ELFSymbolsToModule(dynsyms, - dynsym_section->sh_size, - dynstrs, - dynstr_section->sh_size, + ELFSymbolsToModule(symtab, + symtab_section->sh_size, + strtab, + strtab_section->sh_size, big_endian, ElfClass::kAddrSize, module); found_usable_info = found_usable_info || result; + } else { + // Look in dynsym only if full symbol table was not available. + const Shdr* dynsym_section = + FindElfSectionByName<ElfClass>(".dynsym", SHT_DYNSYM, + sections, names, names_end, + elf_header->e_shnum); + const Shdr* dynstr_section = + FindElfSectionByName<ElfClass>(".dynstr", SHT_STRTAB, + sections, names, names_end, + elf_header->e_shnum); + if (dynsym_section && dynstr_section) { + info->LoadedSection(".dynsym"); + + const uint8_t* dynsyms = + GetOffset<ElfClass, uint8_t>(elf_header, + dynsym_section->sh_offset); + const uint8_t* dynstrs = + GetOffset<ElfClass, uint8_t>(elf_header, + dynstr_section->sh_offset); + bool result = + ELFSymbolsToModule(dynsyms, + dynsym_section->sh_size, + dynstrs, + dynstr_section->sh_size, + big_endian, + ElfClass::kAddrSize, + module); + found_usable_info = found_usable_info || result; + } } } @@ -683,9 +794,19 @@ bool LoadSymbols(const string& obj_file, // Dwarf Call Frame Information (CFI) is actually independent from // the other DWARF debugging information, and can be used alone. const Shdr* dwarf_cfi_section = - FindElfSectionByName<ElfClass>(".debug_frame", debug_section_type, + FindElfSectionByName<ElfClass>(".debug_frame", SHT_PROGBITS, sections, names, names_end, elf_header->e_shnum); + + // .debug_frame section type is SHT_PROGBITS for mips on pnacl toolchains, + // but MIPS_DWARF for regular gnu toolchains, so both need to be checked + if (elf_header->e_machine == EM_MIPS && !dwarf_cfi_section) { + dwarf_cfi_section = + FindElfSectionByName<ElfClass>(".debug_frame", SHT_MIPS_DWARF, + sections, names, names_end, + elf_header->e_shnum); + } + if (dwarf_cfi_section) { // Ignore the return value of this function; even without call frame // information, the other debugging information could be perfectly @@ -738,9 +859,9 @@ bool LoadSymbols(const string& obj_file, names_end, elf_header->e_shnum); if (gnu_debuglink_section) { if (!info->debug_dirs().empty()) { - const char* debuglink_contents = - GetOffset<ElfClass, char>(elf_header, - gnu_debuglink_section->sh_offset); + const uint8_t *debuglink_contents = + GetOffset<ElfClass, uint8_t>(elf_header, + gnu_debuglink_section->sh_offset); string debuglink_file = ReadDebugLink(debuglink_contents, gnu_debuglink_section->sh_size, @@ -791,35 +912,6 @@ const char* ElfArchitecture(const typename ElfClass::Ehdr* elf_header) { } } -// Format the Elf file identifier in IDENTIFIER as a UUID with the -// dashes removed. -string FormatIdentifier(unsigned char identifier[16]) { - char identifier_str[40]; - google_breakpad::FileID::ConvertIdentifierToString( - identifier, - identifier_str, - sizeof(identifier_str)); - string id_no_dash; - for (int i = 0; identifier_str[i] != '\0'; ++i) - if (identifier_str[i] != '-') - id_no_dash += identifier_str[i]; - // Add an extra "0" by the end. PDB files on Windows have an 'age' - // number appended to the end of the file identifier; this isn't - // really used or necessary on other platforms, but be consistent. - id_no_dash += '0'; - return id_no_dash; -} - -// Return the non-directory portion of FILENAME: the portion after the -// last slash, or the whole filename if there are no slashes. -string BaseFileName(const string &filename) { - // Lots of copies! basename's behavior is less than ideal. - char* c_filename = strdup(filename.c_str()); - string base = basename(c_filename); - free(c_filename); - return base; -} - template<typename ElfClass> bool SanitizeDebugFile(const typename ElfClass::Ehdr* debug_elf_header, const string& debuglink_file, @@ -852,19 +944,13 @@ bool SanitizeDebugFile(const typename ElfClass::Ehdr* debug_elf_header, } template<typename ElfClass> -bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header, - const string& obj_filename, - const std::vector<string>& debug_dirs, - const DumpOptions& options, - Module** out_module) { - typedef typename ElfClass::Ehdr Ehdr; - typedef typename ElfClass::Shdr Shdr; - - *out_module = NULL; - - unsigned char identifier[16]; - if (!google_breakpad::FileID::ElfFileIdentifierFromMappedFile(elf_header, - identifier)) { +bool InitModuleForElfClass(const typename ElfClass::Ehdr* elf_header, + const string& obj_filename, + const string& obj_os, + scoped_ptr<Module>& module) { + PageAllocator allocator; + wasteful_vector<uint8_t> identifier(&allocator, kDefaultBuildIdSize); + if (!FileID::ElfFileIdentifierFromMappedFile(elf_header, identifier)) { fprintf(stderr, "%s: unable to generate file identifier\n", obj_filename.c_str()); return false; @@ -877,17 +963,47 @@ bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header, return false; } + char name_buf[NAME_MAX] = {}; + std::string name = google_breakpad::ElfFileSoNameFromMappedFile( + elf_header, name_buf, sizeof(name_buf)) + ? name_buf + : google_breakpad::BaseName(obj_filename); + + // Add an extra "0" at the end. PDB files on Windows have an 'age' + // number appended to the end of the file identifier; this isn't + // really used or necessary on other platforms, but be consistent. + string id = FileID::ConvertIdentifierToUUIDString(identifier) + "0"; + // This is just the raw Build ID in hex. + string code_id = FileID::ConvertIdentifierToString(identifier); + + module.reset(new Module(name, obj_os, architecture, id, code_id)); + + return true; +} + +template<typename ElfClass> +bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header, + const string& obj_filename, + const string& obj_os, + const std::vector<string>& debug_dirs, + const DumpOptions& options, + Module** out_module) { + typedef typename ElfClass::Ehdr Ehdr; + + *out_module = NULL; + + scoped_ptr<Module> module; + if (!InitModuleForElfClass<ElfClass>(elf_header, obj_filename, obj_os, + module)) { + return false; + } + // Figure out what endianness this file is. bool big_endian; if (!ElfEndianness<ElfClass>(elf_header, &big_endian)) return false; - string name = BaseFileName(obj_filename); - string os = "Linux"; - string id = FormatIdentifier(identifier); - LoadSymbolsInfo<ElfClass> info(debug_dirs); - scoped_ptr<Module> module(new Module(name, os, architecture, id)); if (!LoadSymbols<ElfClass>(obj_filename, big_endian, elf_header, !debug_dirs.empty(), &info, options, module.get())) { @@ -902,7 +1018,9 @@ bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header, if (!LoadELF(debuglink_file, &debug_map_wrapper, reinterpret_cast<void**>(&debug_elf_header)) || !SanitizeDebugFile<ElfClass>(debug_elf_header, debuglink_file, - obj_filename, architecture, big_endian)) { + obj_filename, + module->architecture().c_str(), + big_endian)) { return false; } @@ -924,6 +1042,7 @@ namespace google_breakpad { // Not explicitly exported, but not static so it can be used in unit tests. bool ReadSymbolDataInternal(const uint8_t* obj_file, const string& obj_filename, + const string& obj_os, const std::vector<string>& debug_dirs, const DumpOptions& options, Module** module) { @@ -935,24 +1054,27 @@ bool ReadSymbolDataInternal(const uint8_t* obj_file, int elfclass = ElfClass(obj_file); if (elfclass == ELFCLASS32) { return ReadSymbolDataElfClass<ElfClass32>( - reinterpret_cast<const Elf32_Ehdr*>(obj_file), obj_filename, debug_dirs, - options, module); + reinterpret_cast<const Elf32_Ehdr*>(obj_file), obj_filename, obj_os, + debug_dirs, options, module); } if (elfclass == ELFCLASS64) { return ReadSymbolDataElfClass<ElfClass64>( - reinterpret_cast<const Elf64_Ehdr*>(obj_file), obj_filename, debug_dirs, - options, module); + reinterpret_cast<const Elf64_Ehdr*>(obj_file), obj_filename, obj_os, + debug_dirs, options, module); } return false; } -bool WriteSymbolFile(const string &obj_file, +bool WriteSymbolFile(const string &load_path, + const string &obj_file, + const string &obj_os, const std::vector<string>& debug_dirs, const DumpOptions& options, std::ostream &sym_stream) { Module* module; - if (!ReadSymbolData(obj_file, debug_dirs, options, &module)) + if (!ReadSymbolData(load_path, obj_file, obj_os, debug_dirs, options, + &module)) return false; bool result = module->Write(sym_stream, options.symbol_data); @@ -960,17 +1082,62 @@ bool WriteSymbolFile(const string &obj_file, return result; } -bool ReadSymbolData(const string& obj_file, +// Read the selected object file's debugging information, and write out the +// header only to |stream|. Return true on success; if an error occurs, report +// it and return false. +bool WriteSymbolFileHeader(const string& load_path, + const string& obj_file, + const string& obj_os, + std::ostream &sym_stream) { + MmapWrapper map_wrapper; + void* elf_header = NULL; + if (!LoadELF(load_path, &map_wrapper, &elf_header)) { + fprintf(stderr, "Could not load ELF file: %s\n", obj_file.c_str()); + return false; + } + + if (!IsValidElf(elf_header)) { + fprintf(stderr, "Not a valid ELF file: %s\n", obj_file.c_str()); + return false; + } + + int elfclass = ElfClass(elf_header); + scoped_ptr<Module> module; + if (elfclass == ELFCLASS32) { + if (!InitModuleForElfClass<ElfClass32>( + reinterpret_cast<const Elf32_Ehdr*>(elf_header), obj_file, obj_os, + module)) { + fprintf(stderr, "Failed to load ELF module: %s\n", obj_file.c_str()); + return false; + } + } else if (elfclass == ELFCLASS64) { + if (!InitModuleForElfClass<ElfClass64>( + reinterpret_cast<const Elf64_Ehdr*>(elf_header), obj_file, obj_os, + module)) { + fprintf(stderr, "Failed to load ELF module: %s\n", obj_file.c_str()); + return false; + } + } else { + fprintf(stderr, "Unsupported module file: %s\n", obj_file.c_str()); + return false; + } + + return module->Write(sym_stream, ALL_SYMBOL_DATA); +} + +bool ReadSymbolData(const string& load_path, + const string& obj_file, + const string& obj_os, const std::vector<string>& debug_dirs, const DumpOptions& options, Module** module) { MmapWrapper map_wrapper; void* elf_header = NULL; - if (!LoadELF(obj_file, &map_wrapper, &elf_header)) + if (!LoadELF(load_path, &map_wrapper, &elf_header)) return false; return ReadSymbolDataInternal(reinterpret_cast<uint8_t*>(elf_header), - obj_file, debug_dirs, options, module); + obj_file, obj_os, debug_dirs, options, module); } } // namespace google_breakpad diff --git a/src/common/linux/dump_symbols.h b/src/common/linux/dump_symbols.h index 636bb72f..eaddd8b2 100644 --- a/src/common/linux/dump_symbols.h +++ b/src/common/linux/dump_symbols.h @@ -62,15 +62,28 @@ struct DumpOptions { // If OBJ_FILE has been stripped but contains a .gnu_debuglink section, // then look for the debug file in DEBUG_DIRS. // SYMBOL_DATA allows limiting the type of symbol data written. -bool WriteSymbolFile(const string &obj_file, +bool WriteSymbolFile(const string &load_path, + const string &obj_file, + const string &obj_os, const std::vector<string>& debug_dirs, const DumpOptions& options, std::ostream &sym_stream); +// Read the selected object file's debugging information, and write out the +// header only to |stream|. Return true on success; if an error occurs, report +// it and return false. |obj_file| becomes the MODULE file name and |obj_os| +// becomes the MODULE operating system. +bool WriteSymbolFileHeader(const string& load_path, + const string& obj_file, + const string& obj_os, + std::ostream &sym_stream); + // As above, but simply return the debugging information in MODULE // instead of writing it to a stream. The caller owns the resulting // Module object and must delete it when finished. -bool ReadSymbolData(const string& obj_file, +bool ReadSymbolData(const string& load_path, + const string& obj_file, + const string& obj_os, const std::vector<string>& debug_dirs, const DumpOptions& options, Module** module); diff --git a/src/common/linux/dump_symbols_unittest.cc b/src/common/linux/dump_symbols_unittest.cc index 3f86dbe6..54c21096 100644 --- a/src/common/linux/dump_symbols_unittest.cc +++ b/src/common/linux/dump_symbols_unittest.cc @@ -40,6 +40,8 @@ #include <vector> #include "breakpad_googletest_includes.h" +#include "common/linux/elf_gnu_compat.h" +#include "common/linux/elfutils.h" #include "common/linux/dump_symbols.h" #include "common/linux/synth_elf.h" #include "common/module.h" @@ -49,11 +51,13 @@ namespace google_breakpad { bool ReadSymbolDataInternal(const uint8_t* obj_file, const string& obj_filename, + const string& obj_os, const std::vector<string>& debug_dir, const DumpOptions& options, Module** module); using google_breakpad::synth_elf::ELF; +using google_breakpad::synth_elf::Notes; using google_breakpad::synth_elf::StringTable; using google_breakpad::synth_elf::SymbolTable; using google_breakpad::test_assembler::kLittleEndian; @@ -61,7 +65,9 @@ using google_breakpad::test_assembler::Section; using std::stringstream; using std::vector; using ::testing::Test; +using ::testing::Types; +template<typename ElfClass> class DumpSymbols : public Test { public: void GetElfContents(ELF& elf) { @@ -78,20 +84,25 @@ class DumpSymbols : public Test { uint8_t* elfdata; }; -TEST_F(DumpSymbols, Invalid) { +typedef Types<ElfClass32, ElfClass64> ElfClasses; + +TYPED_TEST_SUITE(DumpSymbols, ElfClasses); + +TYPED_TEST(DumpSymbols, Invalid) { Elf32_Ehdr header; memset(&header, 0, sizeof(header)); Module* module; DumpOptions options(ALL_SYMBOL_DATA, true); EXPECT_FALSE(ReadSymbolDataInternal(reinterpret_cast<uint8_t*>(&header), "foo", + "Linux", vector<string>(), options, &module)); } -TEST_F(DumpSymbols, SimplePublic32) { - ELF elf(EM_386, ELFCLASS32, kLittleEndian); +TYPED_TEST(DumpSymbols, SimplePublic) { + ELF elf(TypeParam::kMachine, TypeParam::kClass, kLittleEndian); // Zero out text section for simplicity. Section text(kLittleEndian); text.Append(4096, 0); @@ -99,8 +110,11 @@ TEST_F(DumpSymbols, SimplePublic32) { // Add a public symbol. StringTable table(kLittleEndian); - SymbolTable syms(kLittleEndian, 4, table); - syms.AddSymbol("superfunc", (uint32_t)0x1000, (uint32_t)0x10, + SymbolTable syms(kLittleEndian, TypeParam::kAddrSize, table); + syms.AddSymbol("superfunc", + (typename TypeParam::Addr)0x1000, + (typename TypeParam::Addr)0x10, + // ELF32_ST_INFO works for 32-or 64-bit. ELF32_ST_INFO(STB_GLOBAL, STT_FUNC), SHN_UNDEF + 1); int index = elf.AddSection(".dynstr", table, SHT_STRTAB); @@ -109,39 +123,56 @@ TEST_F(DumpSymbols, SimplePublic32) { SHF_ALLOC, // flags 0, // addr index, // link - sizeof(Elf32_Sym)); // entsize + sizeof(typename TypeParam::Sym)); // entsize elf.Finish(); - GetElfContents(elf); + this->GetElfContents(elf); Module* module; DumpOptions options(ALL_SYMBOL_DATA, true); - EXPECT_TRUE(ReadSymbolDataInternal(elfdata, + EXPECT_TRUE(ReadSymbolDataInternal(this->elfdata, "foo", + "Linux", vector<string>(), options, &module)); stringstream s; module->Write(s, ALL_SYMBOL_DATA); - EXPECT_EQ("MODULE Linux x86 000000000000000000000000000000000 foo\n" - "PUBLIC 1000 0 superfunc\n", - s.str()); + const string expected = + string("MODULE Linux ") + TypeParam::kMachineName + + " 000000000000000000000000000000000 foo\n" + "INFO CODE_ID 00000000000000000000000000000000\n" + "PUBLIC 1000 0 superfunc\n"; + EXPECT_EQ(expected, s.str()); delete module; } -TEST_F(DumpSymbols, SimplePublic64) { - ELF elf(EM_X86_64, ELFCLASS64, kLittleEndian); +TYPED_TEST(DumpSymbols, SimpleBuildID) { + ELF elf(TypeParam::kMachine, TypeParam::kClass, kLittleEndian); // Zero out text section for simplicity. Section text(kLittleEndian); text.Append(4096, 0); elf.AddSection(".text", text, SHT_PROGBITS); + // Add a Build ID + const uint8_t kExpectedIdentifierBytes[] = + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13}; + Notes notes(kLittleEndian); + notes.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifierBytes, + sizeof(kExpectedIdentifierBytes)); + elf.AddSection(".note.gnu.build-id", notes, SHT_NOTE); + // Add a public symbol. StringTable table(kLittleEndian); - SymbolTable syms(kLittleEndian, 8, table); - syms.AddSymbol("superfunc", (uint64_t)0x1000, (uint64_t)0x10, - ELF64_ST_INFO(STB_GLOBAL, STT_FUNC), + SymbolTable syms(kLittleEndian, TypeParam::kAddrSize, table); + syms.AddSymbol("superfunc", + (typename TypeParam::Addr)0x1000, + (typename TypeParam::Addr)0x10, + // ELF32_ST_INFO works for 32-or 64-bit. + ELF32_ST_INFO(STB_GLOBAL, STT_FUNC), SHN_UNDEF + 1); int index = elf.AddSection(".dynstr", table, SHT_STRTAB); elf.AddSection(".dynsym", syms, @@ -149,24 +180,29 @@ TEST_F(DumpSymbols, SimplePublic64) { SHF_ALLOC, // flags 0, // addr index, // link - sizeof(Elf64_Sym)); // entsize + sizeof(typename TypeParam::Sym)); // entsize elf.Finish(); - GetElfContents(elf); + this->GetElfContents(elf); Module* module; DumpOptions options(ALL_SYMBOL_DATA, true); - EXPECT_TRUE(ReadSymbolDataInternal(elfdata, + EXPECT_TRUE(ReadSymbolDataInternal(this->elfdata, "foo", + "Linux", vector<string>(), options, &module)); stringstream s; module->Write(s, ALL_SYMBOL_DATA); - EXPECT_EQ("MODULE Linux x86_64 000000000000000000000000000000000 foo\n" - "PUBLIC 1000 0 superfunc\n", - s.str()); + const string expected = + string("MODULE Linux ") + TypeParam::kMachineName + + " 030201000504070608090A0B0C0D0E0F0 foo\n" + "INFO CODE_ID 000102030405060708090A0B0C0D0E0F10111213\n" + "PUBLIC 1000 0 superfunc\n"; + EXPECT_EQ(expected, s.str()); + delete module; } } // namespace google_breakpad diff --git a/src/common/linux/elf_core_dump.h b/src/common/linux/elf_core_dump.h index d03c7a88..6e153745 100644 --- a/src/common/linux/elf_core_dump.h +++ b/src/common/linux/elf_core_dump.h @@ -34,6 +34,7 @@ #define COMMON_LINUX_ELF_CORE_DUMP_H_ #include <elf.h> +#include <limits.h> #include <link.h> #include <stddef.h> @@ -45,18 +46,18 @@ namespace google_breakpad { // provides methods for accessing program headers and the note section. class ElfCoreDump { public: - // ELF types based on the value of __WORDSIZE. + // ELF types based on the native word size. typedef ElfW(Ehdr) Ehdr; typedef ElfW(Nhdr) Nhdr; typedef ElfW(Phdr) Phdr; typedef ElfW(Word) Word; typedef ElfW(Addr) Addr; -#if __WORDSIZE == 32 +#if ULONG_MAX == 0xffffffff static const int kClass = ELFCLASS32; -#elif __WORDSIZE == 64 +#elif ULONG_MAX == 0xffffffffffffffff static const int kClass = ELFCLASS64; #else -#error "Unsupported __WORDSIZE for ElfCoreDump." +#error "Unsupported word size for ElfCoreDump." #endif // A class encapsulating the note content in a core dump, which provides diff --git a/src/common/linux/elf_core_dump_unittest.cc b/src/common/linux/elf_core_dump_unittest.cc index 9b41dcee..2399c12f 100644 --- a/src/common/linux/elf_core_dump_unittest.cc +++ b/src/common/linux/elf_core_dump_unittest.cc @@ -244,9 +244,18 @@ TEST(ElfCoreDumpTest, ValidCoreFile) { note = note.GetNextNote(); } - EXPECT_TRUE(expected_thread_ids == actual_thread_ids); +#if defined(THREAD_SANITIZER) + for (std::set<pid_t>::const_iterator expected = expected_thread_ids.begin(); + expected != expected_thread_ids.end(); + ++expected) { + EXPECT_NE(actual_thread_ids.find(*expected), actual_thread_ids.end()); + } + EXPECT_GE(num_nt_prstatus, kNumOfThreads); +#else + EXPECT_EQ(actual_thread_ids, expected_thread_ids); + EXPECT_EQ(num_nt_prstatus, kNumOfThreads); +#endif EXPECT_EQ(1U, num_nt_prpsinfo); - EXPECT_EQ(kNumOfThreads, num_nt_prstatus); #if defined(__i386__) || defined(__x86_64__) EXPECT_EQ(num_pr_fpvalid, num_nt_fpregset); #endif diff --git a/src/common/linux/elf_gnu_compat.h b/src/common/linux/elf_gnu_compat.h index f870cbc7..0a3dfedb 100644 --- a/src/common/linux/elf_gnu_compat.h +++ b/src/common/linux/elf_gnu_compat.h @@ -43,4 +43,9 @@ #define NT_GNU_BUILD_ID 3 #endif +// Newer Linux systems offer this. +#ifndef NT_SIGINFO +#define NT_SIGINFO 0x53494749 +#endif + #endif // COMMON_LINUX_ELF_GNU_COMPAT_H_ diff --git a/src/common/linux/elfutils.cc b/src/common/linux/elfutils.cc index a79391c1..9532d5ad 100644 --- a/src/common/linux/elfutils.cc +++ b/src/common/linux/elfutils.cc @@ -78,14 +78,12 @@ void FindElfClassSection(const char *elf_base, template<typename ElfClass> void FindElfClassSegment(const char *elf_base, typename ElfClass::Word segment_type, - const void **segment_start, - size_t *segment_size) { + wasteful_vector<ElfSegment> *segments) { typedef typename ElfClass::Ehdr Ehdr; typedef typename ElfClass::Phdr Phdr; assert(elf_base); - assert(segment_start); - assert(segment_size); + assert(segments); assert(my_strncmp(elf_base, ELFMAG, SELFMAG) == 0); @@ -97,9 +95,10 @@ void FindElfClassSegment(const char *elf_base, for (int i = 0; i < elf_header->e_phnum; ++i) { if (phdrs[i].p_type == segment_type) { - *segment_start = elf_base + phdrs[i].p_offset; - *segment_size = phdrs[i].p_filesz; - return; + ElfSegment seg = {}; + seg.start = elf_base + phdrs[i].p_offset; + seg.size = phdrs[i].p_filesz; + segments->push_back(seg); } } } @@ -122,8 +121,7 @@ bool FindElfSection(const void *elf_mapped_base, const char *section_name, uint32_t section_type, const void **section_start, - size_t *section_size, - int *elfclass) { + size_t *section_size) { assert(elf_mapped_base); assert(section_start); assert(section_size); @@ -135,10 +133,6 @@ bool FindElfSection(const void *elf_mapped_base, return false; int cls = ElfClass(elf_mapped_base); - if (elfclass) { - *elfclass = cls; - } - const char* elf_base = static_cast<const char*>(elf_mapped_base); @@ -155,40 +149,89 @@ bool FindElfSection(const void *elf_mapped_base, return false; } -bool FindElfSegment(const void *elf_mapped_base, - uint32_t segment_type, - const void **segment_start, - size_t *segment_size, - int *elfclass) { +bool FindElfSegments(const void* elf_mapped_base, + uint32_t segment_type, + wasteful_vector<ElfSegment>* segments) { assert(elf_mapped_base); - assert(segment_start); - assert(segment_size); - - *segment_start = NULL; - *segment_size = 0; + assert(segments); if (!IsValidElf(elf_mapped_base)) return false; int cls = ElfClass(elf_mapped_base); - if (elfclass) { - *elfclass = cls; - } - const char* elf_base = static_cast<const char*>(elf_mapped_base); if (cls == ELFCLASS32) { - FindElfClassSegment<ElfClass32>(elf_base, segment_type, - segment_start, segment_size); - return *segment_start != NULL; + FindElfClassSegment<ElfClass32>(elf_base, segment_type, segments); + return true; } else if (cls == ELFCLASS64) { - FindElfClassSegment<ElfClass64>(elf_base, segment_type, - segment_start, segment_size); - return *segment_start != NULL; + FindElfClassSegment<ElfClass64>(elf_base, segment_type, segments); + return true; + } + + return false; +} + +template <typename ElfClass> +bool FindElfSoNameFromDynamicSection(const void* section_start, + size_t section_size, + const void* dynstr_start, + size_t dynstr_size, + char* soname, + size_t soname_size) { + typedef typename ElfClass::Dyn Dyn; + + auto* dynamic = static_cast<const Dyn*>(section_start); + size_t dcount = section_size / sizeof(Dyn); + for (const Dyn* dyn = dynamic; dyn < dynamic + dcount; ++dyn) { + if (dyn->d_tag == DT_SONAME) { + const char* dynstr = static_cast<const char*>(dynstr_start); + if (dyn->d_un.d_val >= dynstr_size) { + // Beyond the end of the dynstr section + return false; + } + const char* str = dynstr + dyn->d_un.d_val; + const size_t maxsize = dynstr_size - dyn->d_un.d_val; + my_strlcpy(soname, str, maxsize < soname_size ? maxsize : soname_size); + return true; + } } return false; } +bool ElfFileSoNameFromMappedFile(const void* elf_base, + char* soname, + size_t soname_size) { + if (!IsValidElf(elf_base)) { + // Not ELF + return false; + } + + const void* segment_start; + size_t segment_size; + if (!FindElfSection(elf_base, ".dynamic", SHT_DYNAMIC, &segment_start, + &segment_size)) { + // No dynamic section + return false; + } + + const void* dynstr_start; + size_t dynstr_size; + if (!FindElfSection(elf_base, ".dynstr", SHT_STRTAB, &dynstr_start, + &dynstr_size)) { + // No dynstr section + return false; + } + + int cls = ElfClass(elf_base); + return cls == ELFCLASS32 ? FindElfSoNameFromDynamicSection<ElfClass32>( + segment_start, segment_size, dynstr_start, + dynstr_size, soname, soname_size) + : FindElfSoNameFromDynamicSection<ElfClass64>( + segment_start, segment_size, dynstr_start, + dynstr_size, soname, soname_size); +} + } // namespace google_breakpad diff --git a/src/common/linux/elfutils.h b/src/common/linux/elfutils.h index dccdc235..aefb6cf5 100644 --- a/src/common/linux/elfutils.h +++ b/src/common/linux/elfutils.h @@ -37,34 +37,46 @@ #include <link.h> #include <stdint.h> +#include "common/memory_allocator.h" + namespace google_breakpad { // Traits classes so consumers can write templatized code to deal // with specific ELF bits. struct ElfClass32 { typedef Elf32_Addr Addr; + typedef Elf32_Dyn Dyn; typedef Elf32_Ehdr Ehdr; typedef Elf32_Nhdr Nhdr; typedef Elf32_Phdr Phdr; typedef Elf32_Shdr Shdr; typedef Elf32_Half Half; typedef Elf32_Off Off; + typedef Elf32_Sym Sym; typedef Elf32_Word Word; + static const int kClass = ELFCLASS32; + static const uint16_t kMachine = EM_386; static const size_t kAddrSize = sizeof(Elf32_Addr); + static constexpr const char* kMachineName = "x86"; }; struct ElfClass64 { typedef Elf64_Addr Addr; + typedef Elf64_Dyn Dyn; typedef Elf64_Ehdr Ehdr; typedef Elf64_Nhdr Nhdr; typedef Elf64_Phdr Phdr; typedef Elf64_Shdr Shdr; typedef Elf64_Half Half; typedef Elf64_Off Off; + typedef Elf64_Sym Sym; typedef Elf64_Word Word; + static const int kClass = ELFCLASS64; + static const uint16_t kMachine = EM_X86_64; static const size_t kAddrSize = sizeof(Elf64_Addr); + static constexpr const char* kMachineName = "x86_64"; }; bool IsValidElf(const void* elf_header); @@ -73,14 +85,12 @@ int ElfClass(const void* elf_base); // Attempt to find a section named |section_name| of type |section_type| // in the ELF binary data at |elf_mapped_base|. On success, returns true // and sets |*section_start| to point to the start of the section data, -// and |*section_size| to the size of the section's data. If |elfclass| -// is not NULL, set |*elfclass| to the ELF file class. +// and |*section_size| to the size of the section's data. bool FindElfSection(const void *elf_mapped_base, const char *section_name, uint32_t section_type, const void **section_start, - size_t *section_size, - int *elfclass); + size_t *section_size); // Internal helper method, exposed for convenience for callers // that already have more info. @@ -93,16 +103,17 @@ FindElfSectionByName(const char* name, const char* names_end, int nsection); -// Attempt to find the first segment of type |segment_type| in the ELF -// binary data at |elf_mapped_base|. On success, returns true and sets -// |*segment_start| to point to the start of the segment data, and -// and |*segment_size| to the size of the segment's data. If |elfclass| -// is not NULL, set |*elfclass| to the ELF file class. -bool FindElfSegment(const void *elf_mapped_base, - uint32_t segment_type, - const void **segment_start, - size_t *segment_size, - int *elfclass); +struct ElfSegment { + const void* start; + size_t size; +}; + +// Attempt to find all segments of type |segment_type| in the ELF +// binary data at |elf_mapped_base|. On success, returns true and fills +// |*segments| with a list of segments of the given type. +bool FindElfSegments(const void* elf_mapped_base, + uint32_t segment_type, + wasteful_vector<ElfSegment>* segments); // Convert an offset from an Elf header into a pointer to the mapped // address in the current process. Takes an extra template parameter @@ -113,6 +124,12 @@ const T* GetOffset(const typename ElfClass::Ehdr* elf_header, typename ElfClass::Off offset); +// Read the value of DT_SONAME from the elf file mapped at |elf_base|. Returns +// true and fills |soname| with the result if found. +bool ElfFileSoNameFromMappedFile(const void* elf_base, + char* soname, + size_t soname_size); + } // namespace google_breakpad #endif // COMMON_LINUX_ELFUTILS_H_ diff --git a/src/common/linux/file_id.cc b/src/common/linux/file_id.cc index 00b37313..67921c45 100644 --- a/src/common/linux/file_id.cc +++ b/src/common/linux/file_id.cc @@ -39,15 +39,20 @@ #include <string.h> #include <algorithm> +#include <string> #include "common/linux/elf_gnu_compat.h" #include "common/linux/elfutils.h" #include "common/linux/linux_libc_support.h" #include "common/linux/memory_mapped_file.h" +#include "common/using_std_string.h" #include "third_party/lss/linux_syscall_support.h" namespace google_breakpad { +// Used in a few places for backwards-compatibility. +const size_t kMDGUIDSize = sizeof(MDGUID); + FileID::FileID(const char* path) : path_(path) {} // ELF note name and desc are 32-bits word padded. @@ -56,10 +61,11 @@ FileID::FileID(const char* path) : path_(path) {} // These functions are also used inside the crashed process, so be safe // and use the syscall/libc wrappers instead of direct syscalls or libc. -template<typename ElfClass> static bool ElfClassBuildIDNoteIdentifier(const void *section, size_t length, - uint8_t identifier[kMDGUIDSize]) { - typedef typename ElfClass::Nhdr Nhdr; + wasteful_vector<uint8_t>& identifier) { + static_assert(sizeof(ElfClass32::Nhdr) == sizeof(ElfClass64::Nhdr), + "Elf32_Nhdr and Elf64_Nhdr should be the same"); + typedef typename ElfClass32::Nhdr Nhdr; const void* section_end = reinterpret_cast<const char*>(section) + length; const Nhdr* note_header = reinterpret_cast<const Nhdr*>(section); @@ -76,39 +82,35 @@ static bool ElfClassBuildIDNoteIdentifier(const void *section, size_t length, return false; } - const char* build_id = reinterpret_cast<const char*>(note_header) + + const uint8_t* build_id = reinterpret_cast<const uint8_t*>(note_header) + sizeof(Nhdr) + NOTE_PADDING(note_header->n_namesz); - // Copy as many bits of the build ID as will fit - // into the GUID space. - my_memset(identifier, 0, kMDGUIDSize); - memcpy(identifier, build_id, - std::min(kMDGUIDSize, (size_t)note_header->n_descsz)); + identifier.insert(identifier.end(), + build_id, + build_id + note_header->n_descsz); return true; } // Attempt to locate a .note.gnu.build-id section in an ELF binary -// and copy as many bytes of it as will fit into |identifier|. -static bool FindElfBuildIDNote(const void *elf_mapped_base, - uint8_t identifier[kMDGUIDSize]) { - void* note_section; - size_t note_size; - int elfclass; - if ((!FindElfSegment(elf_mapped_base, PT_NOTE, - (const void**)¬e_section, ¬e_size, &elfclass) || - note_size == 0) && - (!FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE, - (const void**)¬e_section, ¬e_size, &elfclass) || - note_size == 0)) { - return false; +// and copy it into |identifier|. +static bool FindElfBuildIDNote(const void* elf_mapped_base, + wasteful_vector<uint8_t>& identifier) { + PageAllocator allocator; + // lld normally creates 2 PT_NOTEs, gold normally creates 1. + auto_wasteful_vector<ElfSegment, 2> segs(&allocator); + if (FindElfSegments(elf_mapped_base, PT_NOTE, &segs)) { + for (ElfSegment& seg : segs) { + if (ElfClassBuildIDNoteIdentifier(seg.start, seg.size, identifier)) { + return true; + } + } } - if (elfclass == ELFCLASS32) { - return ElfClassBuildIDNoteIdentifier<ElfClass32>(note_section, note_size, - identifier); - } else if (elfclass == ELFCLASS64) { - return ElfClassBuildIDNoteIdentifier<ElfClass64>(note_section, note_size, - identifier); + void* note_section; + size_t note_size; + if (FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE, + (const void**)¬e_section, ¬e_size)) { + return ElfClassBuildIDNoteIdentifier(note_section, note_size, identifier); } return false; @@ -116,17 +118,21 @@ static bool FindElfBuildIDNote(const void *elf_mapped_base, // Attempt to locate the .text section of an ELF binary and generate // a simple hash by XORing the first page worth of bytes into |identifier|. -static bool HashElfTextSection(const void *elf_mapped_base, - uint8_t identifier[kMDGUIDSize]) { +static bool HashElfTextSection(const void* elf_mapped_base, + wasteful_vector<uint8_t>& identifier) { + identifier.resize(kMDGUIDSize); + void* text_section; size_t text_size; if (!FindElfSection(elf_mapped_base, ".text", SHT_PROGBITS, - (const void**)&text_section, &text_size, NULL) || + (const void**)&text_section, &text_size) || text_size == 0) { return false; } - my_memset(identifier, 0, kMDGUIDSize); + // Only provide |kMDGUIDSize| bytes to keep identifiers produced by this + // function backwards-compatible. + my_memset(&identifier[0], 0, kMDGUIDSize); const uint8_t* ptr = reinterpret_cast<const uint8_t*>(text_section); const uint8_t* ptr_end = ptr + std::min(text_size, static_cast<size_t>(4096)); while (ptr < ptr_end) { @@ -139,7 +145,7 @@ static bool HashElfTextSection(const void *elf_mapped_base, // static bool FileID::ElfFileIdentifierFromMappedFile(const void* base, - uint8_t identifier[kMDGUIDSize]) { + wasteful_vector<uint8_t>& identifier) { // Look for a build id note first. if (FindElfBuildIDNote(base, identifier)) return true; @@ -148,7 +154,7 @@ bool FileID::ElfFileIdentifierFromMappedFile(const void* base, return HashElfTextSection(base, identifier); } -bool FileID::ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]) { +bool FileID::ElfFileIdentifier(wasteful_vector<uint8_t>& identifier) { MemoryMappedFile mapped_file(path_.c_str(), 0); if (!mapped_file.data()) // Should probably check if size >= ElfW(Ehdr)? return false; @@ -156,13 +162,26 @@ bool FileID::ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]) { return ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier); } +// These three functions are not ever called in an unsafe context, so it's OK +// to allocate memory and use libc. +static string bytes_to_hex_string(const uint8_t* bytes, size_t count) { + string result; + for (unsigned int idx = 0; idx < count; ++idx) { + char buf[3]; + snprintf(buf, sizeof(buf), "%02X", bytes[idx]); + result.append(buf); + } + return result; +} + // static -void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize], - char* buffer, int buffer_length) { - uint8_t identifier_swapped[kMDGUIDSize]; +string FileID::ConvertIdentifierToUUIDString( + const wasteful_vector<uint8_t>& identifier) { + uint8_t identifier_swapped[kMDGUIDSize] = { 0 }; // Endian-ness swap to match dump processor expectation. - memcpy(identifier_swapped, identifier, kMDGUIDSize); + memcpy(identifier_swapped, &identifier[0], + std::min(kMDGUIDSize, identifier.size())); uint32_t* data1 = reinterpret_cast<uint32_t*>(identifier_swapped); *data1 = htonl(*data1); uint16_t* data2 = reinterpret_cast<uint16_t*>(identifier_swapped + 4); @@ -170,22 +189,13 @@ void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize], uint16_t* data3 = reinterpret_cast<uint16_t*>(identifier_swapped + 6); *data3 = htons(*data3); - int buffer_idx = 0; - for (unsigned int idx = 0; - (buffer_idx < buffer_length) && (idx < kMDGUIDSize); - ++idx) { - int hi = (identifier_swapped[idx] >> 4) & 0x0F; - int lo = (identifier_swapped[idx]) & 0x0F; - - if (idx == 4 || idx == 6 || idx == 8 || idx == 10) - buffer[buffer_idx++] = '-'; - - buffer[buffer_idx++] = (hi >= 10) ? 'A' + hi - 10 : '0' + hi; - buffer[buffer_idx++] = (lo >= 10) ? 'A' + lo - 10 : '0' + lo; - } + return bytes_to_hex_string(identifier_swapped, kMDGUIDSize); +} - // NULL terminate - buffer[(buffer_idx < buffer_length) ? buffer_idx : buffer_idx - 1] = 0; +// static +string FileID::ConvertIdentifierToString( + const wasteful_vector<uint8_t>& identifier) { + return bytes_to_hex_string(&identifier[0], identifier.size()); } } // namespace google_breakpad diff --git a/src/common/linux/file_id.h b/src/common/linux/file_id.h index 2642722a..4aff021d 100644 --- a/src/common/linux/file_id.h +++ b/src/common/linux/file_id.h @@ -37,10 +37,16 @@ #include <string> #include "common/linux/guid_creator.h" +#include "common/memory_allocator.h" +#include "common/using_std_string.h" namespace google_breakpad { -static const size_t kMDGUIDSize = sizeof(MDGUID); +// GNU binutils' ld defaults to 'sha1', which is 160 bits == 20 bytes, +// so this is enough to fit that, which most binaries will use. +// This is just a sensible default for auto_wasteful_vector so most +// callers can get away with stack allocation. +static const size_t kDefaultBuildIdSize = 20; class FileID { public: @@ -48,29 +54,33 @@ class FileID { ~FileID() {} // Load the identifier for the elf file path specified in the constructor into - // |identifier|. Return false if the identifier could not be created for the - // file. + // |identifier|. + // // The current implementation will look for a .note.gnu.build-id // section and use that as the file id, otherwise it falls back to // XORing the first 4096 bytes of the .text section to generate an identifier. - bool ElfFileIdentifier(uint8_t identifier[kMDGUIDSize]); + bool ElfFileIdentifier(wasteful_vector<uint8_t>& identifier); // Load the identifier for the elf file mapped into memory at |base| into - // |identifier|. Return false if the identifier could not be created for the + // |identifier|. Return false if the identifier could not be created for this // file. - static bool ElfFileIdentifierFromMappedFile(const void* base, - uint8_t identifier[kMDGUIDSize]); + static bool ElfFileIdentifierFromMappedFile( + const void* base, + wasteful_vector<uint8_t>& identifier); + + // Convert the |identifier| data to a string. The string will + // be formatted as a UUID in all uppercase without dashes. + // (e.g., 22F065BBFC9C49F780FE26A7CEBD7BCE). + static string ConvertIdentifierToUUIDString( + const wasteful_vector<uint8_t>& identifier); - // Convert the |identifier| data to a NULL terminated string. The string will - // be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE). - // The |buffer| should be at least 37 bytes long to receive all of the data - // and termination. Shorter buffers will contain truncated data. - static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize], - char* buffer, int buffer_length); + // Convert the entire |identifier| data to a hex string. + static string ConvertIdentifierToString( + const wasteful_vector<uint8_t>& identifier); private: // Storage for the path specified - std::string path_; + string path_; }; } // namespace google_breakpad diff --git a/src/common/linux/file_id_unittest.cc b/src/common/linux/file_id_unittest.cc index 760eae82..f4f9ac45 100644 --- a/src/common/linux/file_id_unittest.cc +++ b/src/common/linux/file_id_unittest.cc @@ -33,6 +33,7 @@ #include <stdlib.h> #include <string> +#include <vector> #include "common/linux/elf_gnu_compat.h" #include "common/linux/elfutils.h" @@ -45,13 +46,11 @@ #include "breakpad_googletest_includes.h" using namespace google_breakpad; -using google_breakpad::ElfClass32; -using google_breakpad::ElfClass64; -using google_breakpad::SafeReadLink; using google_breakpad::synth_elf::ELF; using google_breakpad::synth_elf::Notes; using google_breakpad::test_assembler::kLittleEndian; using google_breakpad::test_assembler::Section; +using std::vector; using ::testing::Types; namespace { @@ -64,6 +63,8 @@ void PopulateSection(Section* section, int size, int prime_number) { section->Append(1, (i % prime_number) % 256); } +typedef wasteful_vector<uint8_t> id_vector; + } // namespace #ifndef __ANDROID__ @@ -87,19 +88,20 @@ TEST(FileIDStripTest, StripSelf) { sprintf(cmdline, "strip \"%s\"", templ.c_str()); ASSERT_EQ(0, system(cmdline)) << "Failed to execute: " << cmdline; - uint8_t identifier1[sizeof(MDGUID)]; - uint8_t identifier2[sizeof(MDGUID)]; + PageAllocator allocator; + id_vector identifier1(&allocator, kDefaultBuildIdSize); + id_vector identifier2(&allocator, kDefaultBuildIdSize); + FileID fileid1(exe_name); EXPECT_TRUE(fileid1.ElfFileIdentifier(identifier1)); FileID fileid2(templ.c_str()); EXPECT_TRUE(fileid2.ElfFileIdentifier(identifier2)); - char identifier_string1[37]; - char identifier_string2[37]; - FileID::ConvertIdentifierToString(identifier1, identifier_string1, - 37); - FileID::ConvertIdentifierToString(identifier2, identifier_string2, - 37); - EXPECT_STREQ(identifier_string1, identifier_string2); + + string identifier_string1 = + FileID::ConvertIdentifierToUUIDString(identifier1); + string identifier_string2 = + FileID::ConvertIdentifierToUUIDString(identifier2); + EXPECT_EQ(identifier_string1, identifier_string2); } #endif // !__ANDROID__ @@ -116,19 +118,31 @@ public: elfdata = &elfdata_v[0]; } + id_vector make_vector() { + return id_vector(&allocator, kDefaultBuildIdSize); + } + + template<size_t N> + string get_file_id(const uint8_t (&data)[N]) { + id_vector expected_identifier(make_vector()); + expected_identifier.insert(expected_identifier.end(), + &data[0], + data + N); + return FileID::ConvertIdentifierToUUIDString(expected_identifier); + } + vector<uint8_t> elfdata_v; uint8_t* elfdata; + PageAllocator allocator; }; typedef Types<ElfClass32, ElfClass64> ElfClasses; -TYPED_TEST_CASE(FileIDTest, ElfClasses); +TYPED_TEST_SUITE(FileIDTest, ElfClasses); TYPED_TEST(FileIDTest, ElfClass) { - uint8_t identifier[sizeof(MDGUID)]; const char expected_identifier_string[] = - "80808080-8080-0000-0000-008080808080"; - char identifier_string[sizeof(expected_identifier_string)]; + "80808080808000000000008080808080"; const size_t kTextSectionSize = 128; ELF elf(EM_386, TypeParam::kClass, kLittleEndian); @@ -140,58 +154,106 @@ TYPED_TEST(FileIDTest, ElfClass) { elf.Finish(); this->GetElfContents(elf); + id_vector identifier(this->make_vector()); EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, identifier)); - FileID::ConvertIdentifierToString(identifier, identifier_string, - sizeof(identifier_string)); - EXPECT_STREQ(expected_identifier_string, identifier_string); + string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); + EXPECT_EQ(expected_identifier_string, identifier_string); } TYPED_TEST(FileIDTest, BuildID) { - const uint8_t kExpectedIdentifier[sizeof(MDGUID)] = + const uint8_t kExpectedIdentifierBytes[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; - char expected_identifier_string[] = - "00000000-0000-0000-0000-000000000000"; - FileID::ConvertIdentifierToString(kExpectedIdentifier, - expected_identifier_string, - sizeof(expected_identifier_string)); + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13}; + const string expected_identifier_string = + this->get_file_id(kExpectedIdentifierBytes); - uint8_t identifier[sizeof(MDGUID)]; - char identifier_string[sizeof(expected_identifier_string)]; + ELF elf(EM_386, TypeParam::kClass, kLittleEndian); + Section text(kLittleEndian); + text.Append(4096, 0); + elf.AddSection(".text", text, SHT_PROGBITS); + Notes notes(kLittleEndian); + notes.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifierBytes, + sizeof(kExpectedIdentifierBytes)); + elf.AddSection(".note.gnu.build-id", notes, SHT_NOTE); + elf.Finish(); + this->GetElfContents(elf); + + id_vector identifier(this->make_vector()); + EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, + identifier)); + EXPECT_EQ(sizeof(kExpectedIdentifierBytes), identifier.size()); + + string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); + EXPECT_EQ(expected_identifier_string, identifier_string); +} + +// Test that a build id note with fewer bytes than usual is handled. +TYPED_TEST(FileIDTest, BuildIDShort) { + const uint8_t kExpectedIdentifierBytes[] = + {0x00, 0x01, 0x02, 0x03}; + const string expected_identifier_string = + this->get_file_id(kExpectedIdentifierBytes); ELF elf(EM_386, TypeParam::kClass, kLittleEndian); Section text(kLittleEndian); text.Append(4096, 0); elf.AddSection(".text", text, SHT_PROGBITS); Notes notes(kLittleEndian); - notes.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifier, - sizeof(kExpectedIdentifier)); + notes.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifierBytes, + sizeof(kExpectedIdentifierBytes)); elf.AddSection(".note.gnu.build-id", notes, SHT_NOTE); elf.Finish(); this->GetElfContents(elf); + id_vector identifier(this->make_vector()); EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, identifier)); + EXPECT_EQ(sizeof(kExpectedIdentifierBytes), identifier.size()); - FileID::ConvertIdentifierToString(identifier, identifier_string, - sizeof(identifier_string)); - EXPECT_STREQ(expected_identifier_string, identifier_string); + string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); + EXPECT_EQ(expected_identifier_string, identifier_string); } -TYPED_TEST(FileIDTest, BuildIDPH) { - const uint8_t kExpectedIdentifier[sizeof(MDGUID)] = +// Test that a build id note with more bytes than usual is handled. +TYPED_TEST(FileIDTest, BuildIDLong) { + const uint8_t kExpectedIdentifierBytes[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; - char expected_identifier_string[] = - "00000000-0000-0000-0000-000000000000"; - FileID::ConvertIdentifierToString(kExpectedIdentifier, - expected_identifier_string, - sizeof(expected_identifier_string)); + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}; + const string expected_identifier_string = + this->get_file_id(kExpectedIdentifierBytes); - uint8_t identifier[sizeof(MDGUID)]; - char identifier_string[sizeof(expected_identifier_string)]; + ELF elf(EM_386, TypeParam::kClass, kLittleEndian); + Section text(kLittleEndian); + text.Append(4096, 0); + elf.AddSection(".text", text, SHT_PROGBITS); + Notes notes(kLittleEndian); + notes.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifierBytes, + sizeof(kExpectedIdentifierBytes)); + elf.AddSection(".note.gnu.build-id", notes, SHT_NOTE); + elf.Finish(); + this->GetElfContents(elf); + + id_vector identifier(this->make_vector()); + EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, + identifier)); + EXPECT_EQ(sizeof(kExpectedIdentifierBytes), identifier.size()); + + string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); + EXPECT_EQ(expected_identifier_string, identifier_string); +} + +TYPED_TEST(FileIDTest, BuildIDPH) { + const uint8_t kExpectedIdentifierBytes[] = + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13}; + const string expected_identifier_string = + this->get_file_id(kExpectedIdentifierBytes); ELF elf(EM_386, TypeParam::kClass, kLittleEndian); Section text(kLittleEndian); @@ -200,31 +262,59 @@ TYPED_TEST(FileIDTest, BuildIDPH) { Notes notes(kLittleEndian); notes.AddNote(0, "Linux", reinterpret_cast<const uint8_t *>("\0x42\0x02\0\0"), 4); - notes.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifier, - sizeof(kExpectedIdentifier)); + notes.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifierBytes, + sizeof(kExpectedIdentifierBytes)); int note_idx = elf.AddSection(".note", notes, SHT_NOTE); elf.AddSegment(note_idx, note_idx, PT_NOTE); elf.Finish(); this->GetElfContents(elf); + id_vector identifier(this->make_vector()); + EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, + identifier)); + EXPECT_EQ(sizeof(kExpectedIdentifierBytes), identifier.size()); + + string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); + EXPECT_EQ(expected_identifier_string, identifier_string); +} + +TYPED_TEST(FileIDTest, BuildIDMultiplePH) { + const uint8_t kExpectedIdentifierBytes[] = + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13}; + const string expected_identifier_string = + this->get_file_id(kExpectedIdentifierBytes); + + ELF elf(EM_386, TypeParam::kClass, kLittleEndian); + Section text(kLittleEndian); + text.Append(4096, 0); + elf.AddSection(".text", text, SHT_PROGBITS); + Notes notes1(kLittleEndian); + notes1.AddNote(0, "Linux", + reinterpret_cast<const uint8_t *>("\0x42\0x02\0\0"), 4); + Notes notes2(kLittleEndian); + notes2.AddNote(NT_GNU_BUILD_ID, "GNU", kExpectedIdentifierBytes, + sizeof(kExpectedIdentifierBytes)); + int note1_idx = elf.AddSection(".note1", notes1, SHT_NOTE); + int note2_idx = elf.AddSection(".note2", notes2, SHT_NOTE); + elf.AddSegment(note1_idx, note1_idx, PT_NOTE); + elf.AddSegment(note2_idx, note2_idx, PT_NOTE); + elf.Finish(); + this->GetElfContents(elf); + + id_vector identifier(this->make_vector()); EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, identifier)); + EXPECT_EQ(sizeof(kExpectedIdentifierBytes), identifier.size()); - FileID::ConvertIdentifierToString(identifier, identifier_string, - sizeof(identifier_string)); - EXPECT_STREQ(expected_identifier_string, identifier_string); + string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); + EXPECT_EQ(expected_identifier_string, identifier_string); } // Test to make sure two files with different text sections produce // different hashes when not using a build id. TYPED_TEST(FileIDTest, UniqueHashes) { - char identifier_string_1[] = - "00000000-0000-0000-0000-000000000000"; - char identifier_string_2[] = - "00000000-0000-0000-0000-000000000000"; - uint8_t identifier_1[sizeof(MDGUID)]; - uint8_t identifier_2[sizeof(MDGUID)]; - { ELF elf1(EM_386, TypeParam::kClass, kLittleEndian); Section foo_1(kLittleEndian); @@ -237,10 +327,11 @@ TYPED_TEST(FileIDTest, UniqueHashes) { this->GetElfContents(elf1); } + id_vector identifier_1(this->make_vector()); EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, identifier_1)); - FileID::ConvertIdentifierToString(identifier_1, identifier_string_1, - sizeof(identifier_string_1)); + string identifier_string_1 = + FileID::ConvertIdentifierToUUIDString(identifier_1); { ELF elf2(EM_386, TypeParam::kClass, kLittleEndian); @@ -254,10 +345,28 @@ TYPED_TEST(FileIDTest, UniqueHashes) { this->GetElfContents(elf2); } + id_vector identifier_2(this->make_vector()); EXPECT_TRUE(FileID::ElfFileIdentifierFromMappedFile(this->elfdata, identifier_2)); - FileID::ConvertIdentifierToString(identifier_2, identifier_string_2, - sizeof(identifier_string_2)); + string identifier_string_2 = + FileID::ConvertIdentifierToUUIDString(identifier_2); - EXPECT_STRNE(identifier_string_1, identifier_string_2); + EXPECT_NE(identifier_string_1, identifier_string_2); +} + +TYPED_TEST(FileIDTest, ConvertIdentifierToString) { + const uint8_t kIdentifierBytes[] = + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}; + const char* kExpected = + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"; + + id_vector identifier(this->make_vector()); + identifier.insert(identifier.end(), + kIdentifierBytes, + kIdentifierBytes + sizeof(kIdentifierBytes)); + ASSERT_EQ(kExpected, + FileID::ConvertIdentifierToString(identifier)); } diff --git a/src/common/linux/google_crashdump_uploader.cc b/src/common/linux/google_crashdump_uploader.cc index 6d86fb36..a0d940b6 100644 --- a/src/common/linux/google_crashdump_uploader.cc +++ b/src/common/linux/google_crashdump_uploader.cc @@ -193,10 +193,15 @@ bool GoogleCrashdumpUploader::Upload(int* http_status_code, return false; } std::cout << "Sending request to " << crash_server_; - return http_layer_->SendRequest(crash_server_, - parameters_, - http_status_code, - http_response_header, - http_response_body); + long status_code; + bool success = http_layer_->SendRequest(crash_server_, + parameters_, + &status_code, + http_response_header, + http_response_body); + if (http_status_code) { + *http_status_code = status_code; + } + return success; } } diff --git a/src/common/linux/google_crashdump_uploader_test.cc b/src/common/linux/google_crashdump_uploader_test.cc index e94c5d62..3d6612e8 100644 --- a/src/common/linux/google_crashdump_uploader_test.cc +++ b/src/common/linux/google_crashdump_uploader_test.cc @@ -50,7 +50,7 @@ class MockLibcurlWrapper : public LibcurlWrapper { MOCK_METHOD5(SendRequest, bool(const string& url, const std::map<string, string>& parameters, - int* http_status_code, + long* http_status_code, string* http_header_data, string* http_response_data)); }; diff --git a/src/common/linux/guid_creator.cc b/src/common/linux/guid_creator.cc index bfb308ee..03e3d781 100644 --- a/src/common/linux/guid_creator.cc +++ b/src/common/linux/guid_creator.cc @@ -27,15 +27,27 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "common/linux/eintr_wrapper.h" #include "common/linux/guid_creator.h" #include <assert.h> +#include <fcntl.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include <sys/stat.h> #include <time.h> #include <unistd.h> +#if defined(HAVE_SYS_RANDOM_H) +#include <sys/random.h> +#endif + // // GUIDGenerator // @@ -61,28 +73,101 @@ class GUIDGenerator { } static bool CreateGUID(GUID *guid) { - InitOnce(); - guid->data1 = random(); - guid->data2 = (uint16_t)(random()); - guid->data3 = (uint16_t)(random()); - UInt32ToBytes(&guid->data4[0], random()); - UInt32ToBytes(&guid->data4[4], random()); +#if defined(HAVE_ARC4RANDOM) // Android, BSD, ... + CreateGuidFromArc4Random(guid); +#else // Linux + bool success = false; + +#if defined(HAVE_SYS_RANDOM_H) && defined(HAVE_GETRANDOM) + success = CreateGUIDFromGetrandom(guid); +#endif // HAVE_SYS_RANDOM_H && HAVE_GETRANDOM + if (!success) { + success = CreateGUIDFromDevUrandom(guid); + } + + if (!success) { + CreateGUIDFromRand(guid); + success = true; + } +#endif + + // Put in the version according to RFC 4122. + guid->data3 &= 0x0fff; + guid->data3 |= 0x4000; + + // Put in the variant according to RFC 4122. + guid->data4[0] &= 0x3f; + guid->data4[0] |= 0x80; + return true; } private: +#ifdef HAVE_ARC4RANDOM + static void CreateGuidFromArc4Random(GUID *guid) { + char *buf = reinterpret_cast<char *>(guid); + + for (size_t i = 0; i < sizeof(GUID); i += sizeof(uint32_t)) { + uint32_t random_data = arc4random(); + + memcpy(buf + i, &random_data, sizeof(uint32_t)); + } + } +#else static void InitOnce() { pthread_once(&once_control, &InitOnceImpl); } static void InitOnceImpl() { - srandom(time(NULL)); + // time(NULL) is a very poor seed, so lacking anything better mix an + // address into it. We drop the four rightmost bits as they're likely to + // be 0 on almost all architectures. + srand(time(NULL) | ((uintptr_t)&once_control >> 4)); } static pthread_once_t once_control; + +#if defined(HAVE_SYS_RANDOM_H) && defined(HAVE_GETRANDOM) + static bool CreateGUIDFromGetrandom(GUID *guid) { + char *buf = reinterpret_cast<char *>(guid); + int read_bytes = getrandom(buf, sizeof(GUID), GRND_NONBLOCK); + + return (read_bytes == static_cast<int>(sizeof(GUID))); + } +#endif // HAVE_SYS_RANDOM_H && HAVE_GETRANDOM + + // Populate the GUID using random bytes read from /dev/urandom, returns false + // if the GUID wasn't fully populated with random data. + static bool CreateGUIDFromDevUrandom(GUID *guid) { + char *buf = reinterpret_cast<char *>(guid); + int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + + if (fd == -1) { + return false; + } + + ssize_t read_bytes = HANDLE_EINTR(read(fd, buf, sizeof(GUID))); + close(fd); + + return (read_bytes == static_cast<ssize_t>(sizeof(GUID))); + } + + // Populate the GUID using a stream of random bytes obtained from rand(). + static void CreateGUIDFromRand(GUID *guid) { + char *buf = reinterpret_cast<char *>(guid); + + InitOnce(); + + for (size_t i = 0; i < sizeof(GUID); i++) { + buf[i] = rand(); + } + } +#endif }; +#ifndef HAVE_ARC4RANDOM pthread_once_t GUIDGenerator::once_control = PTHREAD_ONCE_INIT; +#endif bool CreateGUID(GUID *guid) { return GUIDGenerator::CreateGUID(guid); diff --git a/src/common/linux/http_upload.cc b/src/common/linux/http_upload.cc index d49f2276..702526af 100644 --- a/src/common/linux/http_upload.cc +++ b/src/common/linux/http_upload.cc @@ -56,8 +56,7 @@ static const char kUserAgent[] = "Breakpad/1.0 (Linux)"; // static bool HTTPUpload::SendRequest(const string &url, const map<string, string> ¶meters, - const string &upload_file, - const string &file_part_name, + const map<string, string> &files, const string &proxy, const string &proxy_user_pwd, const string &ca_certificate_file, @@ -73,7 +72,9 @@ bool HTTPUpload::SendRequest(const string &url, // We may have been linked statically; if curl_easy_init is in the // current binary, no need to search for a dynamic version. void* curl_lib = dlopen(NULL, RTLD_NOW); - if (!curl_lib || dlsym(curl_lib, "curl_easy_init") == NULL) { + if (!CheckCurlLib(curl_lib)) { + fprintf(stderr, + "Failed to open curl lib from binary, use libcurl.so instead\n"); dlerror(); // Clear dlerror before attempting to open libraries. dlclose(curl_lib); curl_lib = NULL; @@ -114,6 +115,10 @@ bool HTTPUpload::SendRequest(const string &url, *(void**) (&curl_easy_setopt) = dlsym(curl_lib, "curl_easy_setopt"); (*curl_easy_setopt)(curl, CURLOPT_URL, url.c_str()); (*curl_easy_setopt)(curl, CURLOPT_USERAGENT, kUserAgent); + // Support multithread by disabling timeout handling, would get SIGSEGV with + // Curl_resolv_timeout in stack trace otherwise. + // See https://curl.haxx.se/libcurl/c/threadsafe.html + (*curl_easy_setopt)(curl, CURLOPT_NOSIGNAL, 1); // Set proxy information if necessary. if (!proxy.empty()) (*curl_easy_setopt)(curl, CURLOPT_PROXY, proxy.c_str()); @@ -135,11 +140,13 @@ bool HTTPUpload::SendRequest(const string &url, CURLFORM_COPYCONTENTS, iter->second.c_str(), CURLFORM_END); - // Add form file. - (*curl_formadd)(&formpost, &lastptr, - CURLFORM_COPYNAME, file_part_name.c_str(), - CURLFORM_FILE, upload_file.c_str(), - CURLFORM_END); + // Add form files. + for (iter = files.begin(); iter != files.end(); ++iter) { + (*curl_formadd)(&formpost, &lastptr, + CURLFORM_COPYNAME, iter->first.c_str(), + CURLFORM_FILE, iter->second.c_str(), + CURLFORM_END); + } (*curl_easy_setopt)(curl, CURLOPT_HTTPPOST, formpost); @@ -197,6 +204,13 @@ bool HTTPUpload::SendRequest(const string &url, } // static +bool HTTPUpload::CheckCurlLib(void* curl_lib) { + return curl_lib && + dlsym(curl_lib, "curl_easy_init") && + dlsym(curl_lib, "curl_easy_setopt"); +} + +// static bool HTTPUpload::CheckParameters(const map<string, string> ¶meters) { for (map<string, string>::const_iterator pos = parameters.begin(); pos != parameters.end(); ++pos) { diff --git a/src/common/linux/http_upload.h b/src/common/linux/http_upload.h index 6dd36ea0..bc1d5d57 100644 --- a/src/common/linux/http_upload.h +++ b/src/common/linux/http_upload.h @@ -45,9 +45,9 @@ using std::map; class HTTPUpload { public: - // Sends the given set of parameters, along with the contents of - // upload_file, as a multipart POST request to the given URL. - // file_part_name contains the name of the file part of the request + // Sends the given sets of parameters and files as a multipart POST + // request to the given URL. + // Each key in |files| is the name of the file part of the request // (i.e. it corresponds to the name= attribute on an <input type="file">. // Parameter names must contain only printable ASCII characters, // and may not contain a quote (") character. @@ -60,8 +60,7 @@ class HTTPUpload { // returned in error_description. static bool SendRequest(const string &url, const map<string, string> ¶meters, - const string &upload_file, - const string &file_part_name, + const map<string, string> &files, const string &proxy, const string &proxy_user_pwd, const string &ca_certificate_file, @@ -75,6 +74,9 @@ class HTTPUpload { // any quote (") characters. Returns true if so. static bool CheckParameters(const map<string, string> ¶meters); + // Checks the curl_lib parameter points to a valid curl lib. + static bool CheckCurlLib(void* curl_lib); + // No instances of this class should be created. // Disallow all constructors, destructors, and operator=. HTTPUpload(); diff --git a/src/common/linux/ignore_ret.h b/src/common/linux/ignore_ret.h index f60384bb..efd274c2 100644 --- a/src/common/linux/ignore_ret.h +++ b/src/common/linux/ignore_ret.h @@ -35,6 +35,6 @@ // the call fails, IGNORE_RET() can be used to mark the return code as ignored. // This avoids spurious compiler warnings. -#define IGNORE_RET(x) do { if (x); } while (0) +#define IGNORE_RET(x) do { if (x) {} } while (0) #endif // COMMON_LINUX_IGNORE_RET_H_ diff --git a/src/common/linux/libcurl_wrapper.cc b/src/common/linux/libcurl_wrapper.cc index fd4e34cd..e96c2038 100644 --- a/src/common/linux/libcurl_wrapper.cc +++ b/src/common/linux/libcurl_wrapper.cc @@ -38,32 +38,24 @@ namespace google_breakpad { LibcurlWrapper::LibcurlWrapper() : init_ok_(false), - formpost_(NULL), - lastptr_(NULL), - headerlist_(NULL) { - curl_lib_ = dlopen("libcurl.so", RTLD_NOW); - if (!curl_lib_) { - curl_lib_ = dlopen("libcurl.so.4", RTLD_NOW); - } - if (!curl_lib_) { - curl_lib_ = dlopen("libcurl.so.3", RTLD_NOW); - } - if (!curl_lib_) { - std::cout << "Could not find libcurl via dlopen"; - return; + curl_lib_(nullptr), + last_curl_error_(""), + curl_(nullptr), + formpost_(nullptr), + lastptr_(nullptr), + headerlist_(nullptr) {} + +LibcurlWrapper::~LibcurlWrapper() { + if (init_ok_) { + (*easy_cleanup_)(curl_); + dlclose(curl_lib_); } - std::cout << "LibcurlWrapper init succeeded"; - init_ok_ = true; - return; } -LibcurlWrapper::~LibcurlWrapper() {} - bool LibcurlWrapper::SetProxy(const string& proxy_host, const string& proxy_userpwd) { - if (!init_ok_) { - return false; - } + if (!CheckInit()) return false; + // Set proxy information if necessary. if (!proxy_host.empty()) { (*easy_setopt_)(curl_, CURLOPT_PROXY, proxy_host.c_str()); @@ -83,9 +75,8 @@ bool LibcurlWrapper::SetProxy(const string& proxy_host, bool LibcurlWrapper::AddFile(const string& upload_file_path, const string& basename) { - if (!init_ok_) { - return false; - } + if (!CheckInit()) return false; + std::cout << "Adding " << upload_file_path << " to form upload."; // Add form file. (*formadd_)(&formpost_, &lastptr_, @@ -110,10 +101,11 @@ static size_t WriteCallback(void *ptr, size_t size, bool LibcurlWrapper::SendRequest(const string& url, const std::map<string, string>& parameters, - int* http_status_code, + long* http_status_code, string* http_header_data, string* http_response_data) { - (*easy_setopt_)(curl_, CURLOPT_URL, url.c_str()); + if (!CheckInit()) return false; + std::map<string, string>::const_iterator iter = parameters.begin(); for (; iter != parameters.end(); ++iter) (*formadd_)(&formpost_, &lastptr_, @@ -122,55 +114,91 @@ bool LibcurlWrapper::SendRequest(const string& url, CURLFORM_END); (*easy_setopt_)(curl_, CURLOPT_HTTPPOST, formpost_); - if (http_response_data != NULL) { - http_response_data->clear(); - (*easy_setopt_)(curl_, CURLOPT_WRITEFUNCTION, WriteCallback); - (*easy_setopt_)(curl_, CURLOPT_WRITEDATA, - reinterpret_cast<void *>(http_response_data)); - } - if (http_header_data != NULL) { - http_header_data->clear(); - (*easy_setopt_)(curl_, CURLOPT_HEADERFUNCTION, WriteCallback); - (*easy_setopt_)(curl_, CURLOPT_HEADERDATA, - reinterpret_cast<void *>(http_header_data)); - } - CURLcode err_code = CURLE_OK; - err_code = (*easy_perform_)(curl_); - easy_strerror_ = reinterpret_cast<const char* (*)(CURLcode)> - (dlsym(curl_lib_, "curl_easy_strerror")); + return SendRequestInner(url, http_status_code, http_header_data, + http_response_data); +} - if (http_status_code != NULL) { - (*easy_getinfo_)(curl_, CURLINFO_RESPONSE_CODE, http_status_code); - } +bool LibcurlWrapper::SendGetRequest(const string& url, + long* http_status_code, + string* http_header_data, + string* http_response_data) { + if (!CheckInit()) return false; -#ifndef NDEBUG - if (err_code != CURLE_OK) - fprintf(stderr, "Failed to send http request to %s, error: %s\n", - url.c_str(), - (*easy_strerror_)(err_code)); -#endif - if (headerlist_ != NULL) { - (*slist_free_all_)(headerlist_); - } + (*easy_setopt_)(curl_, CURLOPT_HTTPGET, 1L); - (*easy_cleanup_)(curl_); - if (formpost_ != NULL) { - (*formfree_)(formpost_); + return SendRequestInner(url, http_status_code, http_header_data, + http_response_data); +} + +bool LibcurlWrapper::SendPutRequest(const string& url, + const string& path, + long* http_status_code, + string* http_header_data, + string* http_response_data) { + if (!CheckInit()) return false; + + FILE* file = fopen(path.c_str(), "rb"); + (*easy_setopt_)(curl_, CURLOPT_UPLOAD, 1L); + (*easy_setopt_)(curl_, CURLOPT_PUT, 1L); + (*easy_setopt_)(curl_, CURLOPT_READDATA, file); + + bool success = SendRequestInner(url, http_status_code, http_header_data, + http_response_data); + + fclose(file); + return success; +} + +bool LibcurlWrapper::SendSimplePostRequest(const string& url, + const string& body, + const string& content_type, + long* http_status_code, + string* http_header_data, + string* http_response_data) { + if (!CheckInit()) return false; + + (*easy_setopt_)(curl_, CURLOPT_POSTFIELDSIZE, body.size()); + (*easy_setopt_)(curl_, CURLOPT_COPYPOSTFIELDS, body.c_str()); + + if (!content_type.empty()) { + string content_type_header = "Content-Type: " + content_type; + headerlist_ = (*slist_append_)( + headerlist_, + content_type_header.c_str()); } - return err_code == CURLE_OK; + return SendRequestInner(url, http_status_code, http_header_data, + http_response_data); } bool LibcurlWrapper::Init() { - if (!init_ok_) { - std::cout << "Init_OK was not true in LibcurlWrapper::Init(), check earlier log messages"; + // First check to see if libcurl was statically linked: + curl_lib_ = dlopen(nullptr, RTLD_NOW); + if (curl_lib_ && + (!dlsym(curl_lib_, "curl_easy_init") || + !dlsym(curl_lib_, "curl_easy_setopt"))) { + // Not statically linked, try again below. + dlerror(); // Clear dlerror before attempting to open libraries. + dlclose(curl_lib_); + curl_lib_ = nullptr; + } + if (!curl_lib_) { + curl_lib_ = dlopen("libcurl.so", RTLD_NOW); + } + if (!curl_lib_) { + curl_lib_ = dlopen("libcurl.so.4", RTLD_NOW); + } + if (!curl_lib_) { + curl_lib_ = dlopen("libcurl.so.3", RTLD_NOW); + } + if (!curl_lib_) { + std::cout << "Could not find libcurl via dlopen"; return false; } if (!SetFunctionPointers()) { std::cout << "Could not find function pointers"; - init_ok_ = false; return false; } @@ -184,11 +212,7 @@ bool LibcurlWrapper::Init() { return false; } - // Disable 100-continue header. - char buf[] = "Expect:"; - - headerlist_ = (*slist_append_)(headerlist_, buf); - (*easy_setopt_)(curl_, CURLOPT_HTTPHEADER, headerlist_); + init_ok_ = true; return true; } @@ -228,6 +252,10 @@ bool LibcurlWrapper::SetFunctionPointers() { "curl_easy_getinfo", CURLcode(*)(CURL *, CURLINFO info, ...)); + SET_AND_CHECK_FUNCTION_POINTER(easy_reset_, + "curl_easy_reset", + void(*)(CURL*)); + SET_AND_CHECK_FUNCTION_POINTER(slist_free_all_, "curl_slist_free_all", void(*)(curl_slist*)); @@ -238,4 +266,73 @@ bool LibcurlWrapper::SetFunctionPointers() { return true; } +bool LibcurlWrapper::SendRequestInner(const string& url, + long* http_status_code, + string* http_header_data, + string* http_response_data) { + string url_copy(url); + (*easy_setopt_)(curl_, CURLOPT_URL, url_copy.c_str()); + + // Disable 100-continue header. + char buf[] = "Expect:"; + headerlist_ = (*slist_append_)(headerlist_, buf); + (*easy_setopt_)(curl_, CURLOPT_HTTPHEADER, headerlist_); + + if (http_response_data != nullptr) { + http_response_data->clear(); + (*easy_setopt_)(curl_, CURLOPT_WRITEFUNCTION, WriteCallback); + (*easy_setopt_)(curl_, CURLOPT_WRITEDATA, + reinterpret_cast<void*>(http_response_data)); + } + if (http_header_data != nullptr) { + http_header_data->clear(); + (*easy_setopt_)(curl_, CURLOPT_HEADERFUNCTION, WriteCallback); + (*easy_setopt_)(curl_, CURLOPT_HEADERDATA, + reinterpret_cast<void*>(http_header_data)); + } + CURLcode err_code = CURLE_OK; + err_code = (*easy_perform_)(curl_); + easy_strerror_ = reinterpret_cast<const char* (*)(CURLcode)> + (dlsym(curl_lib_, "curl_easy_strerror")); + + if (http_status_code != nullptr) { + (*easy_getinfo_)(curl_, CURLINFO_RESPONSE_CODE, http_status_code); + } + +#ifndef NDEBUG + if (err_code != CURLE_OK) + fprintf(stderr, "Failed to send http request to %s, error: %s\n", + url.c_str(), + (*easy_strerror_)(err_code)); +#endif + + Reset(); + + return err_code == CURLE_OK; } + +void LibcurlWrapper::Reset() { + if (headerlist_ != nullptr) { + (*slist_free_all_)(headerlist_); + headerlist_ = nullptr; + } + + if (formpost_ != nullptr) { + (*formfree_)(formpost_); + formpost_ = nullptr; + } + + (*easy_reset_)(curl_); +} + +bool LibcurlWrapper::CheckInit() { + if (!init_ok_) { + std::cout << "LibcurlWrapper: You must call Init(), and have it return " + "'true' before invoking any other methods.\n"; + return false; + } + + return true; +} + +} // namespace google_breakpad diff --git a/src/common/linux/libcurl_wrapper.h b/src/common/linux/libcurl_wrapper.h index de84a63b..77aa6cbb 100644 --- a/src/common/linux/libcurl_wrapper.h +++ b/src/common/linux/libcurl_wrapper.h @@ -43,7 +43,7 @@ namespace google_breakpad { class LibcurlWrapper { public: LibcurlWrapper(); - ~LibcurlWrapper(); + virtual ~LibcurlWrapper(); virtual bool Init(); virtual bool SetProxy(const string& proxy_host, const string& proxy_userpwd); @@ -51,14 +51,39 @@ class LibcurlWrapper { const string& basename); virtual bool SendRequest(const string& url, const std::map<string, string>& parameters, - int* http_status_code, + long* http_status_code, string* http_header_data, string* http_response_data); + bool SendGetRequest(const string& url, + long* http_status_code, + string* http_header_data, + string* http_response_data); + bool SendPutRequest(const string& url, + const string& path, + long* http_status_code, + string* http_header_data, + string* http_response_data); + bool SendSimplePostRequest(const string& url, + const string& body, + const string& content_type, + long* http_status_code, + string* http_header_data, + string* http_response_data); + private: // This function initializes class state corresponding to function // pointers into the CURL library. bool SetFunctionPointers(); + bool SendRequestInner(const string& url, + long* http_status_code, + string* http_header_data, + string* http_response_data); + + void Reset(); + + bool CheckInit(); + bool init_ok_; // Whether init succeeded void* curl_lib_; // Pointer to result of dlopen() on // curl library @@ -85,6 +110,7 @@ class LibcurlWrapper { const char* (*easy_strerror_)(CURLcode); void (*easy_cleanup_)(CURL *); CURLcode (*easy_getinfo_)(CURL *, CURLINFO info, ...); + void (*easy_reset_)(CURL*); void (*formfree_)(struct curl_httppost *); }; diff --git a/src/common/linux/memory_mapped_file.cc b/src/common/linux/memory_mapped_file.cc index 592b66c8..4e938269 100644 --- a/src/common/linux/memory_mapped_file.cc +++ b/src/common/linux/memory_mapped_file.cc @@ -87,18 +87,7 @@ bool MemoryMappedFile::Map(const char* path, size_t offset) { return true; } -#if defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__mips__) && _MIPS_SIM == _ABI64) void* data = sys_mmap(NULL, file_len, PROT_READ, MAP_PRIVATE, fd, offset); -#else - if ((offset & 4095) != 0) { - // Not page aligned. - sys_close(fd); - return false; - } - void* data = sys_mmap2( - NULL, file_len, PROT_READ, MAP_PRIVATE, fd, offset >> 12); -#endif sys_close(fd); if (data == MAP_FAILED) { return false; diff --git a/src/common/linux/symbol_collector_client.cc b/src/common/linux/symbol_collector_client.cc new file mode 100644 index 00000000..92b25ddb --- /dev/null +++ b/src/common/linux/symbol_collector_client.cc @@ -0,0 +1,195 @@ +// Copyright (c) 2019 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "common/linux/symbol_collector_client.h" + +#include <stdio.h> + +#include <iostream> +#include <regex> + +#include "common/linux/libcurl_wrapper.h" + +namespace google_breakpad { +namespace sym_upload { + +// static +bool SymbolCollectorClient::CreateUploadUrl( + LibcurlWrapper* libcurl_wrapper, + const string& api_url, + const string& api_key, + UploadUrlResponse* uploadUrlResponse) { + string header, response; + long response_code; + + string url = api_url + "/v1/uploads:create"; + if (!api_key.empty()) { + url += "?key=" + api_key; + } + + if (!libcurl_wrapper->SendSimplePostRequest(url, + /*body=*/"", + /*content_type=*/"", + &response_code, + &header, + &response)) { + printf("Failed to create upload url.\n"); + printf("Response code: %ld\n", response_code); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return false; + } + + // Note camel-case rather than underscores. + std::regex upload_url_regex("\"uploadUrl\": \"([^\"]+)\""); + std::regex upload_key_regex("\"uploadKey\": \"([^\"]+)\""); + + std::smatch upload_url_match; + if (!std::regex_search(response, upload_url_match, upload_url_regex) || + upload_url_match.size() != 2) { + printf("Failed to parse create url response."); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return false; + } + string upload_url = upload_url_match[1].str(); + + std::smatch upload_key_match; + if (!std::regex_search(response, upload_key_match, upload_key_regex) || + upload_key_match.size() != 2) { + printf("Failed to parse create url response."); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return false; + } + string upload_key = upload_key_match[1].str(); + + uploadUrlResponse->upload_url = upload_url; + uploadUrlResponse->upload_key = upload_key; + return true; +} + +// static +CompleteUploadResult SymbolCollectorClient::CompleteUpload( + LibcurlWrapper* libcurl_wrapper, + const string& api_url, + const string& api_key, + const string& upload_key, + const string& debug_file, + const string& debug_id, + const string& type) { + string header, response; + long response_code; + + string url = api_url + "/v1/uploads/" + upload_key + ":complete"; + if (!api_key.empty()) { + url += "?key=" + api_key; + } + string body = + "{ symbol_id: {" + "debug_file: \"" + debug_file + "\", " + "debug_id: \"" + debug_id + "\" }, " + "symbol_upload_type: \"" + type + "\" }"; + + if (!libcurl_wrapper->SendSimplePostRequest(url, + body, + "application/son", + &response_code, + &header, + &response)) { + printf("Failed to complete upload.\n"); + printf("Response code: %ld\n", response_code); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return CompleteUploadResult::Error; + } + + std::regex result_regex("\"result\": \"([^\"]+)\""); + std::smatch result_match; + if (!std::regex_search(response, result_match, result_regex) || + result_match.size() != 2) { + printf("Failed to parse complete upload response."); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return CompleteUploadResult::Error; + } + string result = result_match[1].str(); + + if (result.compare("DUPLICATE_DATA") == 0) { + return CompleteUploadResult::DuplicateData; + } + + return CompleteUploadResult::Ok; +} + +// static +SymbolStatus SymbolCollectorClient::CheckSymbolStatus( + LibcurlWrapper* libcurl_wrapper, + const string& api_url, + const string& api_key, + const string& debug_file, + const string& debug_id) { + string header, response; + long response_code; + string url = api_url + + "/v1/symbols/" + debug_file + "/" + debug_id + ":checkStatus"; + if (!api_key.empty()) { + url += "?key=" + api_key; + } + + if (!libcurl_wrapper->SendGetRequest( + url, + &response_code, + &header, + &response)) { + printf("Failed to check symbol status, error message.\n"); + printf("Response code: %ld\n", response_code); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return SymbolStatus::Unknown; + } + + std::regex status_regex("\"status\": \"([^\"]+)\""); + std::smatch status_match; + if (!std::regex_search(response, status_match, status_regex) || + status_match.size() != 2) { + printf("Failed to parse check symbol status response."); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return SymbolStatus::Unknown; + } + string status = status_match[1].str(); + + return (status.compare("FOUND") == 0) ? + SymbolStatus::Found : + SymbolStatus::Missing; +} + +} // namespace sym_upload +} // namespace google_breakpad diff --git a/src/common/linux/symbol_collector_client.h b/src/common/linux/symbol_collector_client.h new file mode 100644 index 00000000..0e23242a --- /dev/null +++ b/src/common/linux/symbol_collector_client.h @@ -0,0 +1,88 @@ +// Copyright (c) 2019, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef COMMON_LINUX_SYMBOL_COLLECTOR_CLIENT_H_ +#define COMMON_LINUX_SYMBOL_COLLECTOR_CLIENT_H_ + +#include <string> + +#include "common/linux/libcurl_wrapper.h" +#include "common/using_std_string.h" + +namespace google_breakpad { +namespace sym_upload { + +struct UploadUrlResponse { + string upload_url; + string upload_key; +}; + +enum SymbolStatus { + Found, + Missing, + Unknown +}; + +enum CompleteUploadResult { + Ok, + DuplicateData, + Error +}; + +// Helper class to communicate with a sym-upload-v2 service over HTTP/REST, +// via libcurl. +class SymbolCollectorClient { + public: + static bool CreateUploadUrl( + LibcurlWrapper* libcurl_wrapper, + const string& api_url, + const string& api_key, + UploadUrlResponse* uploadUrlResponse); + + static CompleteUploadResult CompleteUpload( + LibcurlWrapper* libcurl_wrapper, + const string& api_url, + const string& api_key, + const string& upload_key, + const string& debug_file, + const string& debug_id, + const string& type); + + static SymbolStatus CheckSymbolStatus( + LibcurlWrapper* libcurl_wrapper, + const string& api_url, + const string& api_key, + const string& debug_file, + const string& debug_id); +}; + +} // namespace sym_upload +} // namespace google_breakpad + +#endif // COMMON_LINUX_SYMBOL_COLLECTOR_CLIENT_H_ diff --git a/src/common/linux/symbol_upload.cc b/src/common/linux/symbol_upload.cc new file mode 100644 index 00000000..87741a0a --- /dev/null +++ b/src/common/linux/symbol_upload.cc @@ -0,0 +1,284 @@ +// Copyright (c) 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// symbol_upload.cc: implemented google_breakpad::sym_upload::Start, a helper +// function for linux symbol upload tool. + +#include "common/linux/symbol_upload.h" + +#include <assert.h> +#include <stdio.h> + +#include <functional> +#include <iostream> +#include <vector> + +#include "common/linux/http_upload.h" +#include "common/linux/libcurl_wrapper.h" +#include "common/linux/symbol_collector_client.h" + +namespace google_breakpad { +namespace sym_upload { + +void TokenizeByChar(const string &source_string, int c, + std::vector<string> *results) { + assert(results); + string::size_type cur_pos = 0, next_pos = 0; + while ((next_pos = source_string.find(c, cur_pos)) != string::npos) { + if (next_pos != cur_pos) + results->push_back(source_string.substr(cur_pos, next_pos - cur_pos)); + cur_pos = next_pos + 1; + } + if (cur_pos < source_string.size() && next_pos != cur_pos) + results->push_back(source_string.substr(cur_pos)); +} + +//============================================================================= +// Parse out the module line which have 5 parts. +// MODULE <os> <cpu> <uuid> <module-name> +bool ModuleDataForSymbolFile(const string &file, + std::vector<string> *module_parts) { + assert(module_parts); + const size_t kModulePartNumber = 5; + FILE* fp = fopen(file.c_str(), "r"); + if (fp) { + char buffer[1024]; + if (fgets(buffer, sizeof(buffer), fp)) { + string line(buffer); + string::size_type line_break_pos = line.find_first_of('\n'); + if (line_break_pos == string::npos) { + assert(0 && "The file is invalid!"); + fclose(fp); + return false; + } + line.resize(line_break_pos); + const char kDelimiter = ' '; + TokenizeByChar(line, kDelimiter, module_parts); + if (module_parts->size() != kModulePartNumber) + module_parts->clear(); + } + fclose(fp); + } + + return module_parts->size() == kModulePartNumber; +} + +//============================================================================= +string CompactIdentifier(const string &uuid) { + std::vector<string> components; + TokenizeByChar(uuid, '-', &components); + string result; + for (size_t i = 0; i < components.size(); ++i) + result += components[i]; + return result; +} + +// |options| describes the current sym_upload options. +// |module_parts| contains the strings parsed from the MODULE entry of the +// Breakpad symbol file being uploaded. +// |compacted_id| is the debug_id from the MODULE entry of the Breakpad symbol +// file being uploaded, with all hyphens removed. +bool SymUploadV1Start( + const Options& options, + std::vector<string> module_parts, + const string& compacted_id) { + std::map<string, string> parameters; + // Add parameters + if (!options.version.empty()) + parameters["version"] = options.version; + + // MODULE <os> <cpu> <uuid> <module-name> + // 0 1 2 3 4 + parameters["os"] = module_parts[1]; + parameters["cpu"] = module_parts[2]; + parameters["debug_file"] = module_parts[4]; + parameters["code_file"] = module_parts[4]; + parameters["debug_identifier"] = compacted_id; + + std::map<string, string> files; + files["symbol_file"] = options.symbolsPath; + + string response, error; + long response_code; + bool success = HTTPUpload::SendRequest(options.uploadURLStr, + parameters, + files, + options.proxy, + options.proxy_user_pwd, + /*ca_certificate_file=*/"", + &response, + &response_code, + &error); + + if (!success) { + printf("Failed to send symbol file: %s\n", error.c_str()); + printf("Response code: %ld\n", response_code); + printf("Response:\n"); + printf("%s\n", response.c_str()); + } else if (response_code == 0) { + printf("Failed to send symbol file: No response code\n"); + } else if (response_code != 200) { + printf("Failed to send symbol file: Response code %ld\n", response_code); + printf("Response:\n"); + printf("%s\n", response.c_str()); + } else { + printf("Successfully sent the symbol file.\n"); + } + + return success; +} + +// |options| describes the current sym_upload options. +// |code_id| is the basename of the module for which symbols are being +// uploaded. +// |debug_id| is the debug_id of the module for which symbols are being +// uploaded. +bool SymUploadV2Start( + const Options& options, + const string& code_file, + const string& debug_id, + const string& type) { + google_breakpad::LibcurlWrapper libcurl_wrapper; + if (!libcurl_wrapper.Init()) { + printf("Failed to init google_breakpad::LibcurlWrapper.\n"); + return false; + } + + if (!options.force) { + SymbolStatus symbolStatus = SymbolCollectorClient::CheckSymbolStatus( + &libcurl_wrapper, + options.uploadURLStr, + options.api_key, + code_file, + debug_id); + if (symbolStatus == SymbolStatus::Found) { + printf("Symbol file already exists, upload aborted." + " Use \"-f\" to overwrite.\n"); + return true; + } else if (symbolStatus == SymbolStatus::Unknown) { + printf("Failed to check for existing symbol.\n"); + return false; + } + } + + UploadUrlResponse uploadUrlResponse; + if (!SymbolCollectorClient::CreateUploadUrl( + &libcurl_wrapper, + options.uploadURLStr, + options.api_key, + &uploadUrlResponse)) { + printf("Failed to create upload URL.\n"); + return false; + } + + string signed_url = uploadUrlResponse.upload_url; + string upload_key = uploadUrlResponse.upload_key; + string header; + string response; + long response_code; + + if (!libcurl_wrapper.SendPutRequest(signed_url, + options.symbolsPath, + &response_code, + &header, + &response)) { + printf("Failed to send symbol file.\n"); + printf("Response code: %ld\n", response_code); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return false; + } else if (response_code == 0) { + printf("Failed to send symbol file: No response code\n"); + return false; + } else if (response_code != 200) { + printf("Failed to send symbol file: Response code %ld\n", response_code); + printf("Response:\n"); + printf("%s\n", response.c_str()); + return false; + } + + CompleteUploadResult completeUploadResult = + SymbolCollectorClient::CompleteUpload(&libcurl_wrapper, + options.uploadURLStr, + options.api_key, + upload_key, + code_file, + debug_id, + type); + if (completeUploadResult == CompleteUploadResult::Error) { + printf("Failed to complete upload.\n"); + return false; + } else if (completeUploadResult == CompleteUploadResult::DuplicateData) { + printf("Uploaded file checksum matched existing file checksum," + " no change necessary.\n"); + } else { + printf("Successfully sent the symbol file.\n"); + } + + return true; +} + +//============================================================================= +void Start(Options* options) { + if (options->upload_protocol == UploadProtocol::SYM_UPLOAD_V2) { + string code_file; + string debug_id; + string type; + + if (options->type.empty() || options->type == kBreakpadSymbolType) { + // Breakpad upload so read these from input file. + std::vector<string> module_parts; + if (!ModuleDataForSymbolFile(options->symbolsPath, &module_parts)) { + fprintf(stderr, "Failed to parse symbol file!\n"); + return; + } + code_file = module_parts[4]; + debug_id = CompactIdentifier(module_parts[3]); + type = kBreakpadSymbolType; + } else { + // Native upload so these must be explicitly set. + code_file = options->code_file; + debug_id = options->debug_id; + type = options->type; + } + + options->success = SymUploadV2Start(*options, code_file, debug_id, type); + } else { + std::vector<string> module_parts; + if (!ModuleDataForSymbolFile(options->symbolsPath, &module_parts)) { + fprintf(stderr, "Failed to parse symbol file!\n"); + return; + } + const string compacted_id = CompactIdentifier(module_parts[3]); + options->success = SymUploadV1Start(*options, module_parts, compacted_id); + } +} + +} // namespace sym_upload +} // namespace google_breakpad diff --git a/src/common/linux/symbol_upload.h b/src/common/linux/symbol_upload.h new file mode 100644 index 00000000..9033152b --- /dev/null +++ b/src/common/linux/symbol_upload.h @@ -0,0 +1,76 @@ +// -*- mode: c++ -*- + +// Copyright (c) 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// symbol_upload.h: helper functions for linux symbol upload tool. + +#ifndef COMMON_LINUX_SYMBOL_UPLOAD_H_ +#define COMMON_LINUX_SYMBOL_UPLOAD_H_ + +#include <string> + +#include "common/using_std_string.h" + +namespace google_breakpad { +namespace sym_upload { + +enum class UploadProtocol { + SYM_UPLOAD_V1, + SYM_UPLOAD_V2, +}; + +constexpr char kBreakpadSymbolType[] = "BREAKPAD"; + +struct Options { + Options() : upload_protocol(UploadProtocol::SYM_UPLOAD_V1), force(false) {} + + string symbolsPath; + string uploadURLStr; + string proxy; + string proxy_user_pwd; + string version; + bool success; + UploadProtocol upload_protocol; + bool force; + string api_key; + + // These only need to be set for native symbol uploads. + string code_file; + string debug_id; + string type; +}; + +// Starts upload to symbol server with options. +void Start(Options* options); + +} // namespace sym_upload +} // namespace google_breakpad + +#endif // COMMON_LINUX_SYMBOL_UPLOAD_H_ diff --git a/src/common/linux/synth_elf.cc b/src/common/linux/synth_elf.cc index b978550f..98e81dab 100644 --- a/src/common/linux/synth_elf.cc +++ b/src/common/linux/synth_elf.cc @@ -213,8 +213,10 @@ void ELF::Finish() { SymbolTable::SymbolTable(Endianness endianness, size_t addr_size, StringTable& table) : Section(endianness), - addr_size_(addr_size), table_(table) { +#ifndef NDEBUG + addr_size_ = addr_size; +#endif assert(addr_size_ == 4 || addr_size_ == 8); } diff --git a/src/common/linux/synth_elf.h b/src/common/linux/synth_elf.h index 330ceae8..1d2a20ca 100644 --- a/src/common/linux/synth_elf.h +++ b/src/common/linux/synth_elf.h @@ -173,7 +173,9 @@ class SymbolTable : public Section { uint64_t size, unsigned info, uint16_t shndx); private: +#ifndef NDEBUG size_t addr_size_; +#endif StringTable& table_; }; diff --git a/src/common/linux/synth_elf_unittest.cc b/src/common/linux/synth_elf_unittest.cc index 3715b6e6..cd74c286 100644 --- a/src/common/linux/synth_elf_unittest.cc +++ b/src/common/linux/synth_elf_unittest.cc @@ -193,7 +193,7 @@ class BasicElf : public Test {}; typedef Types<ElfClass32, ElfClass64> ElfClasses; -TYPED_TEST_CASE(BasicElf, ElfClasses); +TYPED_TEST_SUITE(BasicElf, ElfClasses); TYPED_TEST(BasicElf, EmptyLE) { typedef typename TypeParam::Ehdr Ehdr; diff --git a/src/common/linux/tests/crash_generator.cc b/src/common/linux/tests/crash_generator.cc index c9491f6f..6896a688 100644 --- a/src/common/linux/tests/crash_generator.cc +++ b/src/common/linux/tests/crash_generator.cc @@ -33,6 +33,7 @@ #include "common/linux/tests/crash_generator.h" #include <pthread.h> +#include <sched.h> #include <signal.h> #include <stdio.h> #include <sys/mman.h> @@ -88,7 +89,7 @@ void *thread_function(void *data) { exit(1); } while (true) { - pthread_yield(); + sched_yield(); } } @@ -184,6 +185,12 @@ bool CrashGenerator::CreateChildCrash( pid_t pid = fork(); if (pid == 0) { + // Custom signal handlers, which may have been installed by a test launcher, + // are undesirable in this child. + if (signal(crash_signal, SIG_DFL) == SIG_ERR) { + perror("CrashGenerator: signal"); + exit(1); + } if (chdir(temp_dir_.path().c_str()) == -1) { perror("CrashGenerator: Failed to change directory"); exit(1); @@ -202,7 +209,7 @@ bool CrashGenerator::CreateChildCrash( // On Android the signal sometimes doesn't seem to get sent even though // tkill returns '0'. Retry a couple of times if the signal doesn't get // through on the first go: - // https://code.google.com/p/google-breakpad/issues/detail?id=579 + // https://bugs.chromium.org/p/google-breakpad/issues/detail?id=579 #if defined(__ANDROID__) const int kRetries = 60; const unsigned int kSleepTimeInSeconds = 1; diff --git a/src/common/linux/ucontext_constants.h b/src/common/linux/ucontext_constants.h new file mode 100644 index 00000000..c390508a --- /dev/null +++ b/src/common/linux/ucontext_constants.h @@ -0,0 +1,153 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This header can be included either from a C, C++ or Assembly file. +// Its purpose is to contain constants that must match the offsets of +// various fields in ucontext_t. +// +// They should match the definitions from signal.h. +// +// Used by src/common/linux/breakpad_getcontext.S +// Tested by src/common/linux/breakpad_getcontext_unittest.cc +// +// This header should not be used by anything else. + +#ifndef GOOGLEBREAKPAD_COMMON_LINUX_UCONTEXT_CONSTANTS_H +#define GOOGLEBREAKPAD_COMMON_LINUX_UCONTEXT_CONSTANTS_H + +#if defined(__arm__) + +#define MCONTEXT_GREGS_OFFSET 32 +#define UCONTEXT_SIGMASK_OFFSET 104 + +#elif defined(__aarch64__) + +#define UCONTEXT_SIGMASK_OFFSET 40 + +#define MCONTEXT_GREGS_OFFSET 184 +#define MCONTEXT_SP_OFFSET 432 +#define MCONTEXT_PC_OFFSET 440 +#define MCONTEXT_PSTATE_OFFSET 448 +#define MCONTEXT_EXTENSION_OFFSET 464 + +#define FPSIMD_MAGIC 0x46508001 + +#define FPSIMD_CONTEXT_MAGIC_OFFSET 0 +#define FPSIMD_CONTEXT_SIZE_OFFSET 4 +#define FPSIMD_CONTEXT_FPSR_OFFSET 8 +#define FPSIMD_CONTEXT_FPCR_OFFSET 12 +#define FPSIMD_CONTEXT_VREGS_OFFSET 16 +#define FPSIMD_CONTEXT_SIZE 528 + +#define REGISTER_SIZE 8 +#define SIMD_REGISTER_SIZE 16 + +#elif defined(__i386__) + +#define MCONTEXT_GREGS_OFFSET 20 +#define MCONTEXT_GS_OFFSET (MCONTEXT_GREGS_OFFSET + 0*4) +#define MCONTEXT_FS_OFFSET (MCONTEXT_GREGS_OFFSET + 1*4) +#define MCONTEXT_ES_OFFSET (MCONTEXT_GREGS_OFFSET + 2*4) +#define MCONTEXT_DS_OFFSET (MCONTEXT_GREGS_OFFSET + 3*4) +#define MCONTEXT_EDI_OFFSET (MCONTEXT_GREGS_OFFSET + 4*4) +#define MCONTEXT_ESI_OFFSET (MCONTEXT_GREGS_OFFSET + 5*4) +#define MCONTEXT_EBP_OFFSET (MCONTEXT_GREGS_OFFSET + 6*4) +#define MCONTEXT_ESP_OFFSET (MCONTEXT_GREGS_OFFSET + 7*4) +#define MCONTEXT_EBX_OFFSET (MCONTEXT_GREGS_OFFSET + 8*4) +#define MCONTEXT_EDX_OFFSET (MCONTEXT_GREGS_OFFSET + 9*4) +#define MCONTEXT_ECX_OFFSET (MCONTEXT_GREGS_OFFSET + 10*4) +#define MCONTEXT_EAX_OFFSET (MCONTEXT_GREGS_OFFSET + 11*4) +#define MCONTEXT_TRAPNO_OFFSET (MCONTEXT_GREGS_OFFSET + 12*4) +#define MCONTEXT_ERR_OFFSET (MCONTEXT_GREGS_OFFSET + 13*4) +#define MCONTEXT_EIP_OFFSET (MCONTEXT_GREGS_OFFSET + 14*4) +#define MCONTEXT_CS_OFFSET (MCONTEXT_GREGS_OFFSET + 15*4) +#define MCONTEXT_EFL_OFFSET (MCONTEXT_GREGS_OFFSET + 16*4) +#define MCONTEXT_UESP_OFFSET (MCONTEXT_GREGS_OFFSET + 17*4) +#define MCONTEXT_SS_OFFSET (MCONTEXT_GREGS_OFFSET + 18*4) + +#define UCONTEXT_SIGMASK_OFFSET 108 + +#define UCONTEXT_FPREGS_OFFSET 96 +#if defined(__BIONIC__) +#define UCONTEXT_FPREGS_MEM_OFFSET 116 +#else +#define UCONTEXT_FPREGS_MEM_OFFSET 236 +#endif + +#elif defined(__mips__) + +#if _MIPS_SIM == _ABIO32 +#define MCONTEXT_PC_OFFSET 32 +#define MCONTEXT_GREGS_OFFSET 40 +#define MCONTEXT_FPREGS_OFFSET 296 +#define MCONTEXT_FPC_CSR 556 +#define UCONTEXT_SIGMASK_OFFSET 616 +#else +#define MCONTEXT_GREGS_OFFSET 40 +#define MCONTEXT_FPREGS_OFFSET 296 +#define MCONTEXT_PC_OFFSET 616 +#define MCONTEXT_FPC_CSR 624 +#define UCONTEXT_SIGMASK_OFFSET 640 +#endif + +#elif defined(__x86_64__) + +#define MCONTEXT_GREGS_OFFSET 40 +#define UCONTEXT_SIGMASK_OFFSET 296 + +#define MCONTEXT_GREGS_R8 40 +#define MCONTEXT_GREGS_R9 48 +#define MCONTEXT_GREGS_R10 56 +#define MCONTEXT_GREGS_R11 64 +#define MCONTEXT_GREGS_R12 72 +#define MCONTEXT_GREGS_R13 80 +#define MCONTEXT_GREGS_R14 88 +#define MCONTEXT_GREGS_R15 96 +#define MCONTEXT_GREGS_RDI 104 +#define MCONTEXT_GREGS_RSI 112 +#define MCONTEXT_GREGS_RBP 120 +#define MCONTEXT_GREGS_RBX 128 +#define MCONTEXT_GREGS_RDX 136 +#define MCONTEXT_GREGS_RAX 144 +#define MCONTEXT_GREGS_RCX 152 +#define MCONTEXT_GREGS_RSP 160 +#define MCONTEXT_GREGS_RIP 168 +#define MCONTEXT_FPREGS_PTR 224 +#if defined(__BIONIC__) +#define MCONTEXT_FPREGS_MEM 304 +#else +#define MCONTEXT_FPREGS_MEM 424 +#endif +#define FPREGS_OFFSET_MXCSR 24 + +#else +#error "This header has not been ported for your CPU" +#endif + +#endif // GOOGLEBREAKPAD_COMMON_ANDROID_UCONTEXT_CONSTANTS_H |