Compare commits
375 Commits
linux-dwar
...
chrome_15
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3a642b92dc | ||
![]() |
cc8194139c | ||
![]() |
405bb7aff7 | ||
![]() |
1e9dbde88a | ||
![]() |
e7b75dbcc3 | ||
![]() |
9dfe692010 | ||
![]() |
742e915661 | ||
![]() |
86901970f2 | ||
![]() |
494fa83886 | ||
![]() |
1f17be3171 | ||
![]() |
86643aa489 | ||
![]() |
8d54c75092 | ||
![]() |
912c99fc8e | ||
![]() |
f3b59ca226 | ||
![]() |
9f042be4a7 | ||
![]() |
1a1890a52a | ||
![]() |
8ade75f955 | ||
![]() |
68e380d5a7 | ||
![]() |
93257311a1 | ||
![]() |
ce8d2156e8 | ||
![]() |
60a883212f | ||
![]() |
7b8b8632d3 | ||
![]() |
e6c0689091 | ||
![]() |
8f2b058398 | ||
![]() |
3c59c247e2 | ||
![]() |
ba5ee2c8f5 | ||
![]() |
1b9920afa7 | ||
![]() |
e919fdd63b | ||
![]() |
48550041f9 | ||
![]() |
d01a9f8bc4 | ||
![]() |
1cbdeae78d | ||
![]() |
32d04cdbbc | ||
![]() |
69607b678f | ||
![]() |
3ca4a120de | ||
![]() |
b2f96f314c | ||
![]() |
be55cc8cf0 | ||
![]() |
df33e47bb4 | ||
![]() |
dcebaf42ba | ||
![]() |
94074a84d0 | ||
![]() |
1807e385d7 | ||
![]() |
04023b1f6a | ||
![]() |
fd557e4a97 | ||
![]() |
23c82999a8 | ||
![]() |
3b3f0c3f61 | ||
![]() |
322818287c | ||
![]() |
a4a9922512 | ||
![]() |
b0201df935 | ||
![]() |
88fa7cfc6b | ||
![]() |
fd18beeb5c | ||
![]() |
bf25801d83 | ||
![]() |
68b256aed3 | ||
![]() |
248d340b71 | ||
![]() |
dbf409ca20 | ||
![]() |
d2b6b9ae82 | ||
![]() |
cff9fdbd94 | ||
![]() |
1530800640 | ||
![]() |
0afda6b2c2 | ||
![]() |
5a73dd4a2b | ||
![]() |
ee8c48cc20 | ||
![]() |
ad35fbc56a | ||
![]() |
ed1c70b5b8 | ||
![]() |
d328f2011f | ||
![]() |
eba8cc5172 | ||
![]() |
0df0555e75 | ||
![]() |
9220e0baf1 | ||
![]() |
281d52d944 | ||
![]() |
b904343e14 | ||
![]() |
b7fdc7396d | ||
![]() |
7b8e2b7e09 | ||
![]() |
80745c59de | ||
![]() |
972ae49d47 | ||
![]() |
78aaf6a665 | ||
![]() |
c77fc8a32c | ||
![]() |
fd00ae5f55 | ||
![]() |
816f25011c | ||
![]() |
717bf0ded2 | ||
![]() |
c45b12b422 | ||
![]() |
0344a368de | ||
![]() |
0d9bd40775 | ||
![]() |
cae59b4ae4 | ||
![]() |
7405ecd046 | ||
![]() |
efbe428d83 | ||
![]() |
ef7262d477 | ||
![]() |
b5dfa2834d | ||
![]() |
8322cd6586 | ||
![]() |
e41dc09252 | ||
![]() |
6c05f5ddd9 | ||
![]() |
fb7fdb9426 | ||
![]() |
30b075c4a5 | ||
![]() |
ccc2446863 | ||
![]() |
3665a7d09b | ||
![]() |
d9d863e153 | ||
![]() |
916a34a982 | ||
![]() |
43e6db7e72 | ||
![]() |
f66ff1afd2 | ||
![]() |
67b65a379c | ||
![]() |
4adb6f4f7b | ||
![]() |
bbd8e82a7f | ||
![]() |
3b4ac42ff8 | ||
![]() |
3382d1e0a6 | ||
![]() |
a8c1c466a1 | ||
![]() |
eabfff133d | ||
![]() |
41f998fe5a | ||
![]() |
43378265bf | ||
![]() |
ac05fa05b5 | ||
![]() |
cfc8628092 | ||
![]() |
9c30407f7f | ||
![]() |
08730fc9a6 | ||
![]() |
361f24eac7 | ||
![]() |
d95350e97a | ||
![]() |
5b117cf53a | ||
![]() |
d35f113d02 | ||
![]() |
d192a71e24 | ||
![]() |
e574a2adc6 | ||
![]() |
f801709a68 | ||
![]() |
8b2e6865e5 | ||
![]() |
2b4274afc4 | ||
![]() |
c653618a91 | ||
![]() |
8c33b3e9c9 | ||
![]() |
de2c055770 | ||
![]() |
82a0188946 | ||
![]() |
8d1130a89e | ||
![]() |
4621ee0691 | ||
![]() |
cec12872c4 | ||
![]() |
9a57c16c97 | ||
![]() |
a91e414f84 | ||
![]() |
b6ee7dcb22 | ||
![]() |
378e28e301 | ||
![]() |
efa30c13f2 | ||
![]() |
c40bcc74d9 | ||
![]() |
1efa3f9e10 | ||
![]() |
4b7f1eba64 | ||
![]() |
1fa7c1c4c4 | ||
![]() |
df9901a45d | ||
![]() |
fda6a1e6fe | ||
![]() |
0e3b7020b8 | ||
![]() |
7fde9a879d | ||
![]() |
90a41b68ad | ||
![]() |
b5b5f9e520 | ||
![]() |
9fc5812260 | ||
![]() |
bb87ebd809 | ||
![]() |
f24c2e1952 | ||
![]() |
53a8b1a204 | ||
![]() |
6c7d641dc9 | ||
![]() |
3a7466663c | ||
![]() |
8ca54e486c | ||
![]() |
7d279feb8f | ||
![]() |
513083b598 | ||
![]() |
662b6da59d | ||
![]() |
8e77c078d6 | ||
![]() |
64506d2ed7 | ||
![]() |
aadace2751 | ||
![]() |
34d3a2e0f3 | ||
![]() |
2452f06b16 | ||
![]() |
c2c4192c1b | ||
![]() |
42943c2391 | ||
![]() |
16f2f37682 | ||
![]() |
a453cc24f4 | ||
![]() |
f8bca185b9 | ||
![]() |
8d2c518c0b | ||
![]() |
3b7d8ee362 | ||
![]() |
07a1c07b7b | ||
![]() |
2072e7e7a9 | ||
![]() |
e167e9e61f | ||
![]() |
39edd96373 | ||
![]() |
0b12b34928 | ||
![]() |
2b7ae09319 | ||
![]() |
31b4957eea | ||
![]() |
14889c340f | ||
![]() |
a599ae80aa | ||
![]() |
f5c8f6fb61 | ||
![]() |
3a69e0e1d1 | ||
![]() |
144938cf22 | ||
![]() |
315fd78199 | ||
![]() |
863aa2a74a | ||
![]() |
25b9371026 | ||
![]() |
e4f52d1c27 | ||
![]() |
26ff18d101 | ||
![]() |
44af96cd2d | ||
![]() |
7cdcc98395 | ||
![]() |
2543651766 | ||
![]() |
0ab73db415 | ||
![]() |
0fdc829d32 | ||
![]() |
0b814c1d2b | ||
![]() |
1b8c24f337 | ||
![]() |
97918069d8 | ||
![]() |
6e3869c19f | ||
![]() |
b5b8051a23 | ||
![]() |
a2d5da4831 | ||
![]() |
23eabdedab | ||
![]() |
6f598cc435 | ||
![]() |
615d2c0d6d | ||
![]() |
4ac61acb3a | ||
![]() |
786275e719 | ||
![]() |
c5f5e0ae65 | ||
![]() |
56eac4dd7a | ||
![]() |
0dd6c95b3f | ||
![]() |
8014071440 | ||
![]() |
1c87cc86e3 | ||
![]() |
7fbd77715f | ||
![]() |
e193098543 | ||
![]() |
4f456b8c0d | ||
![]() |
b80b13fd2f | ||
![]() |
f16fbd5e96 | ||
![]() |
6869f2e1b3 | ||
![]() |
1adc07f0ff | ||
![]() |
9c1e16eaaa | ||
![]() |
bd0a7f9da1 | ||
![]() |
75ce593891 | ||
![]() |
35c41e00ee | ||
![]() |
8ffb12eb35 | ||
![]() |
a4067bc197 | ||
![]() |
e4cc9b12ec | ||
![]() |
4f182c746b | ||
![]() |
c0fc538ae4 | ||
![]() |
8e3c63b7f9 | ||
![]() |
b0059c54da | ||
![]() |
0b54af4f91 | ||
![]() |
4abf9d9ebd | ||
![]() |
1273651fcf | ||
![]() |
7b106e34ba | ||
![]() |
951583a8ca | ||
![]() |
bcf885c807 | ||
![]() |
8cf0a52bec | ||
![]() |
7d0d107b17 | ||
![]() |
3d0b3be6c0 | ||
![]() |
6bc523c618 | ||
![]() |
297b1e5a06 | ||
![]() |
f6163f1e29 | ||
![]() |
073a7f6695 | ||
![]() |
52a508dfe2 | ||
![]() |
25b512d64d | ||
![]() |
775c6f7640 | ||
![]() |
d3e4bbb9c7 | ||
![]() |
deb500f6c8 | ||
![]() |
b28be1254c | ||
![]() |
b0ec96cee2 | ||
![]() |
6c97801617 | ||
![]() |
ffedcd4945 | ||
![]() |
eee511f1df | ||
![]() |
c90769edd6 | ||
![]() |
131c0c6a83 | ||
![]() |
f2dfb88b2a | ||
![]() |
56e7a3c65d | ||
![]() |
504280af60 | ||
![]() |
865df5af57 | ||
![]() |
d5b689e7af | ||
![]() |
089003b7f6 | ||
![]() |
1e73bad86e | ||
![]() |
a62dc27d23 | ||
![]() |
b223627d81 | ||
![]() |
c0ec51afe0 | ||
![]() |
87855248f1 | ||
![]() |
9e6b619ad0 | ||
![]() |
5c27539f91 | ||
![]() |
b34b1d8525 | ||
![]() |
2a18f064ce | ||
![]() |
a0aca73851 | ||
![]() |
608d142aaa | ||
![]() |
00dbb73752 | ||
![]() |
f3319839c4 | ||
![]() |
b68b800189 | ||
![]() |
18dd9d0104 | ||
![]() |
43135e6e46 | ||
![]() |
e7e1e1ebf5 | ||
![]() |
c609f474a9 | ||
![]() |
a76aaa1442 | ||
![]() |
0de9f43b87 | ||
![]() |
3e768ed9c0 | ||
![]() |
6d3a825dbf | ||
![]() |
a7eb2329de | ||
![]() |
9244496c6f | ||
![]() |
5431f41508 | ||
![]() |
19374d2636 | ||
![]() |
81aadb99a6 | ||
![]() |
de545c09d0 | ||
![]() |
841ad48a37 | ||
![]() |
5cf2e760b6 | ||
![]() |
19d77e0c33 | ||
![]() |
d6fb5a7c51 | ||
![]() |
6de1b75da4 | ||
![]() |
89f1396fd1 | ||
![]() |
71f7580891 | ||
![]() |
dd5067f391 | ||
![]() |
952f3c7b73 | ||
![]() |
c50e7c604c | ||
![]() |
83e085b7a3 | ||
![]() |
bad560bb69 | ||
![]() |
f480ba1169 | ||
![]() |
bd76cd035c | ||
![]() |
2b48a6a89f | ||
![]() |
7f1455601d | ||
![]() |
89e07bf2c7 | ||
![]() |
6a9ffff696 | ||
![]() |
2214cb9bc1 | ||
![]() |
e87521a989 | ||
![]() |
e53e6af0c1 | ||
![]() |
dc4029a5c2 | ||
![]() |
3e60d65111 | ||
![]() |
1ac84da26d | ||
![]() |
03ebc1d245 | ||
![]() |
384b1c7d7d | ||
![]() |
97f1da43ae | ||
![]() |
057aa1f617 | ||
![]() |
32d1b2882b | ||
![]() |
bc64ee962f | ||
![]() |
8bfcc2683f | ||
![]() |
e15bffe466 | ||
![]() |
f4a106d4b5 | ||
![]() |
2684b4dc19 | ||
![]() |
5251e64f48 | ||
![]() |
7b873221e6 | ||
![]() |
eef5ccc61f | ||
![]() |
cf55ca5b5c | ||
![]() |
e7b2043dd2 | ||
![]() |
330ca2f7c7 | ||
![]() |
0441036f9e | ||
![]() |
e9faf54828 | ||
![]() |
5b787b1911 | ||
![]() |
2832dc011c | ||
![]() |
c65d3e6206 | ||
![]() |
718478d95d | ||
![]() |
910f68a5d4 | ||
![]() |
6f50ca38bb | ||
![]() |
b64d76a3b8 | ||
![]() |
92b1f834d1 | ||
![]() |
5ebd6507e3 | ||
![]() |
0468306245 | ||
![]() |
c823931376 | ||
![]() |
52cb2c6f42 | ||
![]() |
6ed5383245 | ||
![]() |
5a6e1d3f03 | ||
![]() |
9f211b4283 | ||
![]() |
0a5fc5d663 | ||
![]() |
1adb184d42 | ||
![]() |
9276b0d301 | ||
![]() |
7a77f45f79 | ||
![]() |
e4ffacd0d8 | ||
![]() |
f7f9dfcab6 | ||
![]() |
5fb436d5bb | ||
![]() |
315c4f6b20 | ||
![]() |
bdd7ca54cd | ||
![]() |
07466260e2 | ||
![]() |
4969cfc647 | ||
![]() |
08fecb2e43 | ||
![]() |
7f941f990a | ||
![]() |
0397da8e08 | ||
![]() |
bb846bdc98 | ||
![]() |
1c9c0568e0 | ||
![]() |
9dcc03f47d | ||
![]() |
bb618860df | ||
![]() |
0cbd50c975 | ||
![]() |
d95a88c3f1 | ||
![]() |
72a2bff592 | ||
![]() |
3b9b206b5f | ||
![]() |
b1f271a4ec | ||
![]() |
0314e487e4 | ||
![]() |
096992fac7 | ||
![]() |
873064894b | ||
![]() |
927cc8fa2a | ||
![]() |
2712a8c712 | ||
![]() |
ebebd0fcef | ||
![]() |
0e76d1fdf3 | ||
![]() |
96c69639ee | ||
![]() |
5f4fa55598 | ||
![]() |
a3043159f3 | ||
![]() |
cb33b20f75 | ||
![]() |
e4be54eb3e | ||
![]() |
8d70618ffc | ||
![]() |
760d66430e | ||
![]() |
c84c9e7bb3 | ||
![]() |
b73230385c | ||
![]() |
895d3d17ee | ||
![]() |
4ee3ddcd33 | ||
![]() |
ce9178abaa |
44
DEPS
Normal file
44
DEPS
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) 2010, 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.
|
||||
|
||||
# We only use this file to ease the steps of generating projects after
|
||||
# syncing, if we use gclient. All dependencies are svn:externals instead.
|
||||
# If you're not using gclient, you need to run the gyp python script to
|
||||
# generate the projects.
|
||||
# This can be done by the following command (assuming current directory):
|
||||
# src\tools\gyp\gyp.bat src\client\windows\breakpad_client.gyp
|
||||
hooks = [
|
||||
{
|
||||
# A change to a .gyp, .gypi, or to GYP itself should run the generator.
|
||||
"pattern": ".",
|
||||
"action": ["python",
|
||||
"src/src/tools/gyp/gyp",
|
||||
"src/src/client/windows/breakpad_client.gyp"],
|
||||
},
|
||||
]
|
692
Makefile.am
692
Makefile.am
@@ -1,6 +1,6 @@
|
||||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
# Copyright (c) 2006, Google Inc.
|
||||
# Copyright (c) 2010, Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@@ -33,6 +33,8 @@
|
||||
# This allows #includes to be relative to src/
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/src
|
||||
|
||||
# Specify include paths for ac macros
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
## Documentation
|
||||
docdir = $(prefix)/share/doc/$(PACKAGE)-$(VERSION)
|
||||
@@ -47,9 +49,34 @@ dist_doc_DATA = \
|
||||
|
||||
|
||||
## Libraries
|
||||
lib_LTLIBRARIES = src/libbreakpad.la
|
||||
noinst_LIBRARIES =
|
||||
lib_LIBRARIES =
|
||||
bin_PROGRAMS =
|
||||
check_PROGRAMS =
|
||||
|
||||
src_libbreakpad_la_SOURCES = \
|
||||
if !DISABLE_PROCESSOR
|
||||
lib_LIBRARIES += src/libbreakpad.a
|
||||
noinst_LIBRARIES += src/third_party/libdisasm/libdisasm.a
|
||||
endif
|
||||
|
||||
if LINUX_HOST
|
||||
lib_LIBRARIES += src/client/linux/libbreakpad_client.a
|
||||
|
||||
src_client_linux_libbreakpad_client_a_SOURCES = \
|
||||
src/client/linux/crash_generation/crash_generation_client.cc \
|
||||
src/client/linux/handler/exception_handler.cc \
|
||||
src/client/linux/minidump_writer/linux_dumper.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer.cc \
|
||||
src/client/minidump_file_writer.cc \
|
||||
src/common/convert_UTF.c \
|
||||
src/common/md5.c \
|
||||
src/common/string_conversion.cc \
|
||||
src/common/linux/file_id.cc \
|
||||
src/common/linux/guid_creator.cc
|
||||
endif LINUX_HOST
|
||||
|
||||
if !DISABLE_PROCESSOR
|
||||
src_libbreakpad_a_SOURCES = \
|
||||
src/google_breakpad/common/breakpad_types.h \
|
||||
src/google_breakpad/common/minidump_format.h \
|
||||
src/google_breakpad/common/minidump_size.h \
|
||||
@@ -57,10 +84,13 @@ src_libbreakpad_la_SOURCES = \
|
||||
src/google_breakpad/processor/call_stack.h \
|
||||
src/google_breakpad/processor/code_module.h \
|
||||
src/google_breakpad/processor/code_modules.h \
|
||||
src/google_breakpad/processor/exploitability.h \
|
||||
src/google_breakpad/processor/fast_source_line_resolver.h \
|
||||
src/google_breakpad/processor/memory_region.h \
|
||||
src/google_breakpad/processor/minidump.h \
|
||||
src/google_breakpad/processor/minidump_processor.h \
|
||||
src/google_breakpad/processor/process_state.h \
|
||||
src/google_breakpad/processor/source_line_resolver_base.h \
|
||||
src/google_breakpad/processor/source_line_resolver_interface.h \
|
||||
src/google_breakpad/processor/stack_frame.h \
|
||||
src/google_breakpad/processor/stack_frame_cpu.h \
|
||||
@@ -72,15 +102,34 @@ src_libbreakpad_la_SOURCES = \
|
||||
src/processor/basic_code_module.h \
|
||||
src/processor/basic_code_modules.cc \
|
||||
src/processor/basic_code_modules.h \
|
||||
src/processor/basic_source_line_resolver_types.h \
|
||||
src/processor/basic_source_line_resolver.cc \
|
||||
src/processor/binarystream.h \
|
||||
src/processor/binarystream.cc \
|
||||
src/processor/call_stack.cc \
|
||||
src/processor/cfi_frame_info.cc \
|
||||
src/processor/cfi_frame_info.h \
|
||||
src/processor/contained_range_map-inl.h \
|
||||
src/processor/contained_range_map.h \
|
||||
src/processor/disassembler_x86.h \
|
||||
src/processor/disassembler_x86.cc \
|
||||
src/processor/exploitability.cc \
|
||||
src/processor/exploitability_win.h \
|
||||
src/processor/exploitability_win.cc \
|
||||
src/processor/fast_source_line_resolver_types.h \
|
||||
src/processor/fast_source_line_resolver.cc \
|
||||
src/processor/linked_ptr.h \
|
||||
src/processor/logging.h \
|
||||
src/processor/logging.cc \
|
||||
src/processor/map_serializers-inl.h \
|
||||
src/processor/map_serializers.h \
|
||||
src/processor/minidump.cc \
|
||||
src/processor/minidump_processor.cc \
|
||||
src/processor/module_comparer.cc \
|
||||
src/processor/module_comparer.h \
|
||||
src/processor/module_factory.h \
|
||||
src/processor/module_serializer.cc \
|
||||
src/processor/module_serializer.h \
|
||||
src/processor/pathname_stripper.cc \
|
||||
src/processor/pathname_stripper.h \
|
||||
src/processor/postfix_evaluator-inl.h \
|
||||
@@ -89,123 +138,621 @@ src_libbreakpad_la_SOURCES = \
|
||||
src/processor/range_map-inl.h \
|
||||
src/processor/range_map.h \
|
||||
src/processor/scoped_ptr.h \
|
||||
src/processor/simple_serializer-inl.h \
|
||||
src/processor/simple_serializer.h \
|
||||
src/processor/simple_symbol_supplier.cc \
|
||||
src/processor/simple_symbol_supplier.h \
|
||||
src/processor/stack_frame_info.h \
|
||||
src/processor/windows_frame_info.h \
|
||||
src/processor/source_line_resolver_base_types.h \
|
||||
src/processor/source_line_resolver_base.cc \
|
||||
src/processor/stackwalker.cc \
|
||||
src/processor/stackwalker_amd64.cc \
|
||||
src/processor/stackwalker_amd64.h \
|
||||
src/processor/stackwalker_arm.cc \
|
||||
src/processor/stackwalker_arm.h \
|
||||
src/processor/stackwalker_ppc.cc \
|
||||
src/processor/stackwalker_ppc.h \
|
||||
src/processor/stackwalker_sparc.cc \
|
||||
src/processor/stackwalker_sparc.h \
|
||||
src/processor/stackwalker_x86.cc \
|
||||
src/processor/stackwalker_x86.h
|
||||
src/processor/stackwalker_x86.h \
|
||||
src/processor/static_address_map-inl.h \
|
||||
src/processor/static_address_map.h \
|
||||
src/processor/static_contained_range_map-inl.h \
|
||||
src/processor/static_contained_range_map.h \
|
||||
src/processor/static_map_iterator-inl.h \
|
||||
src/processor/static_map_iterator.h \
|
||||
src/processor/static_map-inl.h \
|
||||
src/processor/static_map.h \
|
||||
src/processor/static_range_map-inl.h \
|
||||
src/processor/static_range_map.h \
|
||||
src/processor/tokenize.cc \
|
||||
src/processor/tokenize.h
|
||||
|
||||
src_libbreakpad_a_LIBADD = src/third_party/libdisasm/libdisasm.a
|
||||
|
||||
src_third_party_libdisasm_libdisasm_a_SOURCES = \
|
||||
src/third_party/libdisasm/ia32_implicit.c \
|
||||
src/third_party/libdisasm/ia32_implicit.h \
|
||||
src/third_party/libdisasm/ia32_insn.c \
|
||||
src/third_party/libdisasm/ia32_insn.h \
|
||||
src/third_party/libdisasm/ia32_invariant.c \
|
||||
src/third_party/libdisasm/ia32_invariant.h \
|
||||
src/third_party/libdisasm/ia32_modrm.c \
|
||||
src/third_party/libdisasm/ia32_modrm.h \
|
||||
src/third_party/libdisasm/ia32_opcode_tables.c \
|
||||
src/third_party/libdisasm/ia32_opcode_tables.h \
|
||||
src/third_party/libdisasm/ia32_operand.c \
|
||||
src/third_party/libdisasm/ia32_operand.h \
|
||||
src/third_party/libdisasm/ia32_reg.c \
|
||||
src/third_party/libdisasm/ia32_reg.h \
|
||||
src/third_party/libdisasm/ia32_settings.c \
|
||||
src/third_party/libdisasm/ia32_settings.h \
|
||||
src/third_party/libdisasm/libdis.h \
|
||||
src/third_party/libdisasm/qword.h \
|
||||
src/third_party/libdisasm/x86_disasm.c \
|
||||
src/third_party/libdisasm/x86_format.c \
|
||||
src/third_party/libdisasm/x86_imm.c \
|
||||
src/third_party/libdisasm/x86_imm.h \
|
||||
src/third_party/libdisasm/x86_insn.c \
|
||||
src/third_party/libdisasm/x86_misc.c \
|
||||
src/third_party/libdisasm/x86_operand_list.c \
|
||||
src/third_party/libdisasm/x86_operand_list.h
|
||||
|
||||
## Programs
|
||||
bin_PROGRAMS = \
|
||||
bin_PROGRAMS += \
|
||||
src/processor/minidump_dump \
|
||||
src/processor/minidump_stackwalk
|
||||
endif !DISABLE_PROCESSOR
|
||||
|
||||
if LINUX_HOST
|
||||
bin_PROGRAMS += \
|
||||
src/client/linux/linux_dumper_unittest_helper
|
||||
|
||||
if !DISABLE_TOOLS
|
||||
bin_PROGRAMS += \
|
||||
src/tools/linux/dump_syms/dump_syms \
|
||||
src/tools/linux/md2core/minidump-2-core \
|
||||
src/tools/linux/symupload/minidump_upload \
|
||||
src/tools/linux/symupload/sym_upload
|
||||
endif
|
||||
endif LINUX_HOST
|
||||
|
||||
|
||||
## Tests
|
||||
check_PROGRAMS = \
|
||||
if !DISABLE_PROCESSOR
|
||||
check_PROGRAMS += \
|
||||
src/common/test_assembler_unittest \
|
||||
src/processor/address_map_unittest \
|
||||
src/processor/binarystream_unittest \
|
||||
src/processor/basic_source_line_resolver_unittest \
|
||||
src/processor/cfi_frame_info_unittest \
|
||||
src/processor/contained_range_map_unittest \
|
||||
src/processor/disassembler_x86_unittest \
|
||||
src/processor/exploitability_unittest \
|
||||
src/processor/fast_source_line_resolver_unittest \
|
||||
src/processor/map_serializers_unittest \
|
||||
src/processor/minidump_processor_unittest \
|
||||
src/processor/minidump_unittest \
|
||||
src/processor/static_address_map_unittest \
|
||||
src/processor/static_contained_range_map_unittest \
|
||||
src/processor/static_map_unittest \
|
||||
src/processor/static_range_map_unittest \
|
||||
src/processor/pathname_stripper_unittest \
|
||||
src/processor/postfix_evaluator_unittest \
|
||||
src/processor/range_map_unittest
|
||||
src/processor/range_map_unittest \
|
||||
src/processor/stackwalker_amd64_unittest \
|
||||
src/processor/stackwalker_arm_unittest \
|
||||
src/processor/stackwalker_x86_unittest \
|
||||
src/processor/synth_minidump_unittest
|
||||
endif
|
||||
|
||||
if LINUX_HOST
|
||||
check_PROGRAMS += \
|
||||
src/client/linux/linux_client_unittest
|
||||
|
||||
if !DISABLE_TOOLS
|
||||
check_PROGRAMS += \
|
||||
src/common/dumper_unittest
|
||||
endif
|
||||
endif LINUX_HOST
|
||||
|
||||
if !DISABLE_PROCESSOR
|
||||
if SELFTEST
|
||||
check_PROGRAMS += \
|
||||
src/processor/stackwalker_selftest
|
||||
endif SELFTEST
|
||||
endif !DISABLE_PROCESSOR
|
||||
|
||||
if !DISABLE_PROCESSOR
|
||||
check_SCRIPTS = \
|
||||
src/processor/minidump_dump_test \
|
||||
src/processor/minidump_stackwalk_test \
|
||||
src/processor/minidump_stackwalk_machine_readable_test
|
||||
endif
|
||||
|
||||
TESTS = $(check_PROGRAMS) $(check_SCRIPTS)
|
||||
TESTS_ENVIRONMENT =
|
||||
|
||||
if LINUX_HOST
|
||||
src_client_linux_linux_dumper_unittest_helper_SOURCES = \
|
||||
src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
|
||||
src_client_linux_linux_dumper_unittest_helper_CXXFLAGS=$(PTHREAD_CFLAGS)
|
||||
src_client_linux_linux_dumper_unittest_helper_LDFLAGS=$(PTHREAD_CFLAGS)
|
||||
src_client_linux_linux_dumper_unittest_helper_CC=$(PTHREAD_CC)
|
||||
|
||||
src_client_linux_linux_client_unittest_SOURCES = \
|
||||
src/client/linux/handler/exception_handler_unittest.cc \
|
||||
src/client/linux/minidump_writer/directory_reader_unittest.cc \
|
||||
src/client/linux/minidump_writer/line_reader_unittest.cc \
|
||||
src/client/linux/minidump_writer/linux_dumper_unittest.cc \
|
||||
src/client/linux/minidump_writer/minidump_writer_unittest.cc \
|
||||
src/common/memory_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc \
|
||||
src/processor/basic_code_modules.cc \
|
||||
src/processor/logging.cc \
|
||||
src/processor/minidump.cc \
|
||||
src/processor/pathname_stripper.cc
|
||||
|
||||
src_client_linux_linux_client_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_client_linux_linux_client_unittest_LDADD = \
|
||||
src/client/linux/handler/exception_handler.o \
|
||||
src/client/linux/crash_generation/crash_generation_client.o \
|
||||
src/client/linux/minidump_writer/linux_dumper.o \
|
||||
src/client/linux/minidump_writer/minidump_writer.o \
|
||||
src/client/minidump_file_writer.o \
|
||||
src/common/convert_UTF.o \
|
||||
src/common/md5.o \
|
||||
src/common/linux/file_id.o \
|
||||
src/common/linux/guid_creator.o \
|
||||
src/common/string_conversion.o
|
||||
|
||||
src_client_linux_linux_client_unittest_DEPENDENCIES = src/client/linux/linux_dumper_unittest_helper src/client/linux/libbreakpad_client.a src/libbreakpad.a
|
||||
|
||||
if !DISABLE_TOOLS
|
||||
src_tools_linux_dump_syms_dump_syms_SOURCES = \
|
||||
src/common/dwarf_cfi_to_module.cc \
|
||||
src/common/dwarf_cu_to_module.cc \
|
||||
src/common/dwarf_line_to_module.cc \
|
||||
src/common/language.cc \
|
||||
src/common/module.cc \
|
||||
src/common/stabs_reader.cc \
|
||||
src/common/stabs_to_module.cc \
|
||||
src/common/dwarf/bytereader.cc \
|
||||
src/common/dwarf/dwarf2diehandler.cc \
|
||||
src/common/dwarf/dwarf2reader.cc \
|
||||
src/common/linux/dump_symbols.cc \
|
||||
src/common/linux/elf_symbols_to_module.cc \
|
||||
src/common/linux/file_id.cc \
|
||||
src/tools/linux/dump_syms/dump_syms.cc
|
||||
|
||||
src_tools_linux_md2core_minidump_2_core_SOURCES = \
|
||||
src/tools/linux/md2core/minidump-2-core.cc
|
||||
|
||||
src_tools_linux_symupload_minidump_upload_SOURCES = \
|
||||
src/common/linux/http_upload.cc \
|
||||
src/tools/linux/symupload/minidump_upload.cc
|
||||
src_tools_linux_symupload_minidump_upload_LDFLAGS = -ldl
|
||||
|
||||
src_tools_linux_symupload_sym_upload_SOURCES = \
|
||||
src/common/linux/http_upload.cc \
|
||||
src/tools/linux/symupload/sym_upload.cc
|
||||
src_tools_linux_symupload_sym_upload_LDFLAGS = -ldl
|
||||
|
||||
src_common_dumper_unittest_SOURCES = \
|
||||
src/common/byte_cursor_unittest.cc \
|
||||
src/common/dwarf_cfi_to_module.cc \
|
||||
src/common/dwarf_cfi_to_module_unittest.cc \
|
||||
src/common/dwarf_cu_to_module.cc \
|
||||
src/common/dwarf_cu_to_module_unittest.cc \
|
||||
src/common/dwarf_line_to_module.cc \
|
||||
src/common/dwarf_line_to_module_unittest.cc \
|
||||
src/common/language.cc \
|
||||
src/common/module.cc \
|
||||
src/common/module_unittest.cc \
|
||||
src/common/stabs_reader.cc \
|
||||
src/common/stabs_reader_unittest.cc \
|
||||
src/common/stabs_to_module.cc \
|
||||
src/common/stabs_to_module_unittest.cc \
|
||||
src/common/test_assembler.cc \
|
||||
src/common/dwarf/bytereader.cc \
|
||||
src/common/dwarf/bytereader_unittest.cc \
|
||||
src/common/dwarf/cfi_assembler.cc \
|
||||
src/common/dwarf/dwarf2diehandler.cc \
|
||||
src/common/dwarf/dwarf2diehandler_unittest.cc \
|
||||
src/common/dwarf/dwarf2reader.cc \
|
||||
src/common/dwarf/dwarf2reader_cfi_unittest.cc \
|
||||
src/common/linux/dump_symbols.cc \
|
||||
src/common/linux/dump_symbols_unittest.cc \
|
||||
src/common/linux/elf_symbols_to_module.cc \
|
||||
src/common/linux/elf_symbols_to_module_unittest.cc \
|
||||
src/common/linux/synth_elf.cc \
|
||||
src/common/linux/synth_elf_unittest.cc \
|
||||
src/common/linux/file_id.cc \
|
||||
src/common/linux/file_id_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_common_dumper_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
endif
|
||||
endif LINUX_HOST
|
||||
|
||||
if !DISABLE_PROCESSOR
|
||||
src_processor_address_map_unittest_SOURCES = \
|
||||
src/processor/address_map_unittest.cc
|
||||
src_processor_address_map_unittest_LDADD = \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_binarystream_unittest_SOURCES = \
|
||||
src/processor/binarystream_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_binarystream_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_binarystream_unittest_LDADD = \
|
||||
src/processor/binarystream.o
|
||||
|
||||
src_processor_basic_source_line_resolver_unittest_SOURCES = \
|
||||
src/processor/basic_source_line_resolver_unittest.cc
|
||||
src/processor/basic_source_line_resolver_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_basic_source_line_resolver_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_basic_source_line_resolver_unittest_LDADD = \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/pathname_stripper.lo \
|
||||
src/processor/logging.lo
|
||||
src/processor/basic_source_line_resolver.o \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/pathname_stripper.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/source_line_resolver_base.o \
|
||||
src/processor/tokenize.o
|
||||
|
||||
src_processor_cfi_frame_info_unittest_SOURCES = \
|
||||
src/processor/cfi_frame_info_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_cfi_frame_info_unittest_LDADD = \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
src_processor_cfi_frame_info_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
|
||||
src_processor_contained_range_map_unittest_SOURCES = \
|
||||
src/processor/contained_range_map_unittest.cc
|
||||
src_processor_contained_range_map_unittest_LDADD = \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_exploitability_unittest_SOURCES = \
|
||||
src/processor/exploitability_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_exploitability_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_exploitability_unittest_LDADD = \
|
||||
src/processor/minidump_processor.o \
|
||||
src/processor/process_state.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/exploitability.o \
|
||||
src/processor/exploitability_win.o \
|
||||
src/processor/basic_code_modules.o \
|
||||
src/processor/basic_source_line_resolver.o \
|
||||
src/processor/call_stack.o \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/minidump.o \
|
||||
src/processor/pathname_stripper.o \
|
||||
src/processor/source_line_resolver_base.o \
|
||||
src/processor/stackwalker.o \
|
||||
src/processor/stackwalker_amd64.o \
|
||||
src/processor/stackwalker_arm.o \
|
||||
src/processor/stackwalker_ppc.o \
|
||||
src/processor/stackwalker_sparc.o \
|
||||
src/processor/stackwalker_x86.o \
|
||||
src/processor/tokenize.o \
|
||||
src/third_party/libdisasm/libdisasm.a
|
||||
|
||||
src_processor_disassembler_x86_unittest_SOURCES = \
|
||||
src/processor/disassembler_x86_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_disassembler_x86_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_disassembler_x86_unittest_LDADD = \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/third_party/libdisasm/libdisasm.a
|
||||
|
||||
src_processor_fast_source_line_resolver_unittest_SOURCES = \
|
||||
src/processor/fast_source_line_resolver_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_fast_source_line_resolver_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_fast_source_line_resolver_unittest_LDADD = \
|
||||
src/processor/fast_source_line_resolver.o \
|
||||
src/processor/basic_source_line_resolver.o \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/module_comparer.o \
|
||||
src/processor/module_serializer.o \
|
||||
src/processor/pathname_stripper.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/source_line_resolver_base.o \
|
||||
src/processor/tokenize.o
|
||||
|
||||
src_processor_map_serializers_unittest_SOURCES = \
|
||||
src/processor/map_serializers_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_map_serializers_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_map_serializers_unittest_LDADD = \
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_minidump_processor_unittest_SOURCES = \
|
||||
src/processor/minidump_processor_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_minidump_processor_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_minidump_processor_unittest_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/minidump_processor.lo \
|
||||
src/processor/minidump.lo \
|
||||
src/processor/pathname_stripper.lo \
|
||||
src/processor/process_state.lo \
|
||||
src/processor/stackwalker.lo \
|
||||
src/processor/stackwalker_amd64.lo \
|
||||
src/processor/stackwalker_ppc.lo \
|
||||
src/processor/stackwalker_sparc.lo \
|
||||
src/processor/stackwalker_x86.lo
|
||||
src/processor/basic_code_modules.o \
|
||||
src/processor/basic_source_line_resolver.o \
|
||||
src/processor/call_stack.o \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/exploitability.o \
|
||||
src/processor/exploitability_win.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/minidump_processor.o \
|
||||
src/processor/minidump.o \
|
||||
src/processor/pathname_stripper.o \
|
||||
src/processor/process_state.o \
|
||||
src/processor/source_line_resolver_base.o \
|
||||
src/processor/stackwalker.o \
|
||||
src/processor/stackwalker_amd64.o \
|
||||
src/processor/stackwalker_arm.o \
|
||||
src/processor/stackwalker_ppc.o \
|
||||
src/processor/stackwalker_sparc.o \
|
||||
src/processor/stackwalker_x86.o \
|
||||
src/processor/tokenize.o \
|
||||
src/third_party/libdisasm/libdisasm.a
|
||||
|
||||
src_processor_minidump_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
src/processor/minidump_unittest.cc \
|
||||
src/processor/synth_minidump.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_minidump_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_minidump_unittest_LDADD = \
|
||||
src/processor/basic_code_modules.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/minidump.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_static_address_map_unittest_SOURCES = \
|
||||
src/processor/static_address_map_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_static_address_map_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_static_address_map_unittest_LDADD = \
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_static_contained_range_map_unittest_SOURCES = \
|
||||
src/processor/static_contained_range_map_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_static_contained_range_map_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_static_contained_range_map_unittest_LDADD = \
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_static_map_unittest_SOURCES = \
|
||||
src/processor/static_map_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_static_map_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_static_map_unittest_LDADD = \
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_static_range_map_unittest_SOURCES = \
|
||||
src/processor/static_range_map_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_static_range_map_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
src_processor_static_range_map_unittest_LDADD = \
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_pathname_stripper_unittest_SOURCES = \
|
||||
src/processor/pathname_stripper_unittest.cc
|
||||
src_processor_pathname_stripper_unittest_LDADD = \
|
||||
src/processor/pathname_stripper.lo
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_postfix_evaluator_unittest_SOURCES = \
|
||||
src/processor/postfix_evaluator_unittest.cc
|
||||
src_processor_postfix_evaluator_unittest_LDADD = \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_range_map_unittest_SOURCES = \
|
||||
src/processor/range_map_unittest.cc
|
||||
src_processor_range_map_unittest_LDADD = \
|
||||
src/processor/logging.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
src/processor/logging.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_stackwalker_selftest_SOURCES = \
|
||||
src/processor/stackwalker_selftest.cc
|
||||
src_processor_stackwalker_selftest_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/minidump.lo \
|
||||
src/processor/pathname_stripper.lo \
|
||||
src/processor/stackwalker.lo \
|
||||
src/processor/stackwalker_amd64.lo \
|
||||
src/processor/stackwalker_ppc.lo \
|
||||
src/processor/stackwalker_sparc.lo \
|
||||
src/processor/stackwalker_x86.lo
|
||||
src/processor/basic_code_modules.o \
|
||||
src/processor/basic_source_line_resolver.o \
|
||||
src/processor/call_stack.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/exploitability.o \
|
||||
src/processor/exploitability_win.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/minidump.o \
|
||||
src/processor/pathname_stripper.o \
|
||||
src/processor/source_line_resolver_base.o \
|
||||
src/processor/stackwalker.o \
|
||||
src/processor/stackwalker_amd64.o \
|
||||
src/processor/stackwalker_arm.o \
|
||||
src/processor/stackwalker_ppc.o \
|
||||
src/processor/stackwalker_sparc.o \
|
||||
src/processor/stackwalker_x86.o \
|
||||
src/processor/tokenize.o
|
||||
|
||||
src_processor_stackwalker_amd64_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
src/processor/stackwalker_amd64_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_stackwalker_amd64_unittest_LDADD = \
|
||||
src/libbreakpad.a
|
||||
src_processor_stackwalker_amd64_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
|
||||
src_processor_stackwalker_arm_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
src/processor/stackwalker_arm_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_stackwalker_arm_unittest_LDADD = \
|
||||
src/libbreakpad.a
|
||||
src_processor_stackwalker_arm_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
|
||||
src_processor_stackwalker_x86_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
src/processor/stackwalker_x86_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_processor_stackwalker_x86_unittest_LDADD = \
|
||||
src/libbreakpad.a
|
||||
src_processor_stackwalker_x86_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
|
||||
src_processor_synth_minidump_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
src/common/test_assembler.h \
|
||||
src/processor/synth_minidump_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc \
|
||||
src/processor/synth_minidump.cc \
|
||||
src/processor/synth_minidump.h
|
||||
src_processor_synth_minidump_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
|
||||
src_common_test_assembler_unittest_SOURCES = \
|
||||
src/common/test_assembler.cc \
|
||||
src/common/test_assembler.h \
|
||||
src/common/test_assembler_unittest.cc \
|
||||
src/testing/gtest/src/gtest-all.cc \
|
||||
src/testing/gtest/src/gtest_main.cc \
|
||||
src/testing/src/gmock-all.cc
|
||||
src_common_test_assembler_unittest_CPPFLAGS = \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/src/testing/include \
|
||||
-I$(top_srcdir)/src/testing/gtest/include \
|
||||
-I$(top_srcdir)/src/testing/gtest \
|
||||
-I$(top_srcdir)/src/testing
|
||||
|
||||
## Non-installables
|
||||
noinst_PROGRAMS =
|
||||
@@ -214,29 +761,39 @@ noinst_SCRIPTS = $(check_SCRIPTS)
|
||||
src_processor_minidump_dump_SOURCES = \
|
||||
src/processor/minidump_dump.cc
|
||||
src_processor_minidump_dump_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/minidump.lo \
|
||||
src/processor/pathname_stripper.lo
|
||||
src/processor/basic_code_modules.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/minidump.o \
|
||||
src/processor/pathname_stripper.o
|
||||
|
||||
src_processor_minidump_stackwalk_SOURCES = \
|
||||
src/processor/minidump_stackwalk.cc
|
||||
src_processor_minidump_stackwalk_LDADD = \
|
||||
src/processor/basic_code_modules.lo \
|
||||
src/processor/basic_source_line_resolver.lo \
|
||||
src/processor/call_stack.lo \
|
||||
src/processor/logging.lo \
|
||||
src/processor/minidump.lo \
|
||||
src/processor/minidump_processor.lo \
|
||||
src/processor/pathname_stripper.lo \
|
||||
src/processor/process_state.lo \
|
||||
src/processor/simple_symbol_supplier.lo \
|
||||
src/processor/stackwalker.lo \
|
||||
src/processor/stackwalker_amd64.lo \
|
||||
src/processor/stackwalker_ppc.lo \
|
||||
src/processor/stackwalker_sparc.lo \
|
||||
src/processor/stackwalker_x86.lo
|
||||
src/processor/basic_code_modules.o \
|
||||
src/processor/basic_source_line_resolver.o \
|
||||
src/processor/binarystream.o \
|
||||
src/processor/call_stack.o \
|
||||
src/processor/cfi_frame_info.o \
|
||||
src/processor/disassembler_x86.o \
|
||||
src/processor/exploitability.o \
|
||||
src/processor/exploitability_win.o \
|
||||
src/processor/logging.o \
|
||||
src/processor/minidump.o \
|
||||
src/processor/minidump_processor.o \
|
||||
src/processor/pathname_stripper.o \
|
||||
src/processor/process_state.o \
|
||||
src/processor/simple_symbol_supplier.o \
|
||||
src/processor/source_line_resolver_base.o \
|
||||
src/processor/stackwalker.o \
|
||||
src/processor/stackwalker_amd64.o \
|
||||
src/processor/stackwalker_arm.o \
|
||||
src/processor/stackwalker_ppc.o \
|
||||
src/processor/stackwalker_sparc.o \
|
||||
src/processor/stackwalker_x86.o \
|
||||
src/processor/tokenize.o \
|
||||
src/third_party/libdisasm/libdisasm.a
|
||||
|
||||
endif !DISABLE_PROCESSOR
|
||||
|
||||
## Additional files to be included in a source distribution
|
||||
##
|
||||
@@ -291,6 +848,8 @@ EXTRA_DIST = \
|
||||
src/common/convert_UTF.h \
|
||||
src/common/linux/dump_symbols.cc \
|
||||
src/common/linux/dump_symbols.h \
|
||||
src/common/linux/elf_symbols_to_module.cc \
|
||||
src/common/linux/elf_symbols_to_module.h \
|
||||
src/common/linux/file_id.cc \
|
||||
src/common/linux/file_id.h \
|
||||
src/common/linux/guid_creator.cc \
|
||||
@@ -373,8 +932,3 @@ EXTRA_DIST = \
|
||||
src/tools/windows/dump_syms/testdata/dump_syms_regtest.sym \
|
||||
src/tools/windows/symupload/symupload.cc \
|
||||
src/tools/windows/symupload/symupload.vcproj
|
||||
|
||||
|
||||
## Additional rules
|
||||
libtool: $(LIBTOOL_DEPS)
|
||||
$(SHELL) ./config.status --recheck
|
||||
|
5013
Makefile.in
5013
Makefile.in
File diff suppressed because it is too large
Load Diff
41
README
41
README
@@ -1,2 +1,43 @@
|
||||
Breakpad is a set of client and server components which implement a
|
||||
crash-reporting system.
|
||||
|
||||
|
||||
-----
|
||||
Getting started in 32-bit mode (from trunk)
|
||||
Configure: CXXFLAGS=-m32 CFLAGS=-m32 CPPFLAGS=-m32 ./configure
|
||||
Build: make
|
||||
Test: make check
|
||||
Install: make install
|
||||
|
||||
If you need to reconfigure your build be sure to run "make distclean" first.
|
||||
|
||||
|
||||
-----
|
||||
To request change review:
|
||||
0. Get access to a read-write copy of source.
|
||||
Owners at http://code.google.com/p/google-breakpad/ are able to grant
|
||||
this access.
|
||||
|
||||
1. Check out a read-write copy of source using instructions at
|
||||
http://code.google.com/p/google-breakpad/source/checkout
|
||||
|
||||
2. Make changes. Build and test your changes.
|
||||
For core code like processor use methods above.
|
||||
For linux/mac/windows, there are test targets in each project file.
|
||||
|
||||
3. Download http://codereview.appspot.com/static/upload.py
|
||||
|
||||
4. Run upload.py from the 'src' directory:
|
||||
upload.py --server=breakpad.appspot.com
|
||||
|
||||
You will be prompted for credential and a description.
|
||||
|
||||
5. At http://breakpad.appspot.com you'll find your issue listed; click on it,
|
||||
and select Publish+Mail, and enter in the code reviewer and CC
|
||||
google-breakpad-dev@googlegroups.com
|
||||
|
||||
6. When applying code review feedback, specify the '-i' option when running
|
||||
upload.py again and pass the issue number so it updates the existing issue,
|
||||
rather than creating a new one.
|
||||
Be sure to rerun upload.py from the same directory as you did for previous
|
||||
uploads to allow for proper diff calculations.
|
||||
|
6990
aclocal.m4
vendored
6990
aclocal.m4
vendored
File diff suppressed because it is too large
Load Diff
1
autotools/compile
Symbolic link
1
autotools/compile
Symbolic link
@@ -0,0 +1 @@
|
||||
/usr/share/automake-1.11/compile
|
8503
autotools/ltmain.sh
8503
autotools/ltmain.sh
File diff suppressed because it is too large
Load Diff
5
codereview.settings
Normal file
5
codereview.settings
Normal file
@@ -0,0 +1,5 @@
|
||||
# This file is used by gcl to get repository specific information.
|
||||
CODE_REVIEW_SERVER: breakpad.appspot.com
|
||||
CC_LIST: google-breakpad-dev@googlegroups.com
|
||||
TRY_ON_UPLOAD: False
|
||||
VIEW_VC: http://code.google.com/p/google-breakpad/source/detail?r=
|
81
configure.ac
81
configure.ac
@@ -30,22 +30,93 @@
|
||||
|
||||
AC_PREREQ(2.57)
|
||||
|
||||
AC_INIT(breakpad, 0.1, opensource@google.com)
|
||||
AC_INIT(breakpad, 0.1, google-breakpad-dev@googlegroups.com)
|
||||
dnl Sanity check: the argument is just a file that should exist.
|
||||
AC_CONFIG_SRCDIR(README)
|
||||
AC_CONFIG_AUX_DIR(autotools)
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CANONICAL_HOST
|
||||
|
||||
AM_INIT_AUTOMAKE(subdir-objects tar-ustar)
|
||||
AM_INIT_AUTOMAKE(subdir-objects tar-ustar 1.11.1)
|
||||
AM_CONFIG_HEADER(src/config.h)
|
||||
|
||||
AC_PROG_CC
|
||||
AM_PROG_CC_C_O
|
||||
AC_PROG_CPP
|
||||
AC_PROG_CXX
|
||||
|
||||
AC_PROG_LIBTOOL
|
||||
AC_SUBST(LIBTOOL_DEPS)
|
||||
AC_PROG_RANLIB
|
||||
|
||||
AC_HEADER_STDC
|
||||
m4_include(m4/ax_pthread.m4)
|
||||
AX_PTHREAD
|
||||
AC_CHECK_HEADERS([a.out.h])
|
||||
|
||||
# Only build Linux client libs when compiling for Linux
|
||||
case $host in
|
||||
*-*-linux* | *-android* )
|
||||
LINUX_HOST=true
|
||||
;;
|
||||
esac
|
||||
AM_CONDITIONAL(LINUX_HOST, test x$LINUX_HOST = xtrue)
|
||||
|
||||
AC_ARG_ENABLE(m32,
|
||||
AS_HELP_STRING([--enable-m32],
|
||||
[Compile/build with -m32]
|
||||
[(default is no)]),
|
||||
[case "${enableval}" in
|
||||
yes)
|
||||
CFLAGS="${CFLAGS} -m32"
|
||||
CXXFLAGS="${CXXFLAGS} -m32"
|
||||
usem32=true
|
||||
;;
|
||||
no)
|
||||
usem32=false
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR(bad value ${enableval} for --enable-m32)
|
||||
;;
|
||||
esac],
|
||||
[usem32=false])
|
||||
|
||||
AC_ARG_ENABLE(processor,
|
||||
AS_HELP_STRING([--disable-processor],
|
||||
[Don't build processor library]
|
||||
[(default is no)]),
|
||||
[case "${enableval}" in
|
||||
yes)
|
||||
disable_processor=false
|
||||
;;
|
||||
no)
|
||||
disable_processor=true
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR(bad value ${enableval} for --disable-processor)
|
||||
;;
|
||||
esac],
|
||||
[disable_processor=false])
|
||||
AM_CONDITIONAL(DISABLE_PROCESSOR, test x$disable_processor = xtrue)
|
||||
|
||||
AC_ARG_ENABLE(tools,
|
||||
AS_HELP_STRING([--disable-tools],
|
||||
[Don't build tool binaries]
|
||||
[(default is no)]),
|
||||
[case "${enableval}" in
|
||||
yes)
|
||||
disable_tools=false
|
||||
;;
|
||||
no)
|
||||
disable_tools=true
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR(bad value ${enableval} for --disable-tools)
|
||||
;;
|
||||
esac],
|
||||
[disable_tools=false])
|
||||
AM_CONDITIONAL(DISABLE_TOOLS, test x$disable_tools = xtrue)
|
||||
|
||||
if test x$LINUX_HOST = xfalse -a x$disable_processor = xtrue -a x$disable_tools = xtrue; then
|
||||
AC_MSG_ERROR([--disable-processor and --disable-tools were specified, and not building for Linux. Nothing to build!])
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE(selftest,
|
||||
AS_HELP_STRING([--enable-selftest],
|
||||
|
283
m4/ax_pthread.m4
Normal file
283
m4/ax_pthread.m4
Normal file
@@ -0,0 +1,283 @@
|
||||
# ===========================================================================
|
||||
# http://www.nongnu.org/autoconf-archive/ax_pthread.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# This macro figures out how to build C programs using POSIX threads. It
|
||||
# sets the PTHREAD_LIBS output variable to the threads library and linker
|
||||
# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
|
||||
# flags that are needed. (The user can also force certain compiler
|
||||
# flags/libs to be tested by setting these environment variables.)
|
||||
#
|
||||
# Also sets PTHREAD_CC to any special C compiler that is needed for
|
||||
# multi-threaded programs (defaults to the value of CC otherwise). (This
|
||||
# is necessary on AIX to use the special cc_r compiler alias.)
|
||||
#
|
||||
# NOTE: You are assumed to not only compile your program with these flags,
|
||||
# but also link it with them as well. e.g. you should link with
|
||||
# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
|
||||
#
|
||||
# If you are only building threads programs, you may wish to use these
|
||||
# variables in your default LIBS, CFLAGS, and CC:
|
||||
#
|
||||
# LIBS="$PTHREAD_LIBS $LIBS"
|
||||
# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||
# CC="$PTHREAD_CC"
|
||||
#
|
||||
# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
|
||||
# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name
|
||||
# (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
|
||||
#
|
||||
# ACTION-IF-FOUND is a list of shell commands to run if a threads library
|
||||
# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
|
||||
# is not found. If ACTION-IF-FOUND is not specified, the default action
|
||||
# will define HAVE_PTHREAD.
|
||||
#
|
||||
# Please let the authors know if this macro fails on any platform, or if
|
||||
# you have any other suggestions or comments. This macro was based on work
|
||||
# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
|
||||
# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
|
||||
# Alejandro Forero Cuervo to the autoconf macro repository. We are also
|
||||
# grateful for the helpful feedback of numerous users.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||
# gives unlimited permission to copy, distribute and modify the configure
|
||||
# scripts that are the output of Autoconf when processing the Macro. You
|
||||
# need not follow the terms of the GNU General Public License when using
|
||||
# or distributing such scripts, even though portions of the text of the
|
||||
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||
# all other use of the material that constitutes the Autoconf Macro.
|
||||
#
|
||||
# This special exception to the GPL applies to versions of the Autoconf
|
||||
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||
# modified version of the Autoconf Macro, you may extend this special
|
||||
# exception to the GPL to apply to your modified version as well.
|
||||
|
||||
#serial 6
|
||||
|
||||
AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
|
||||
AC_DEFUN([AX_PTHREAD], [
|
||||
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||
AC_LANG_SAVE
|
||||
AC_LANG_C
|
||||
ax_pthread_ok=no
|
||||
|
||||
# We used to check for pthread.h first, but this fails if pthread.h
|
||||
# requires special compiler flags (e.g. on True64 or Sequent).
|
||||
# It gets checked for in the link test anyway.
|
||||
|
||||
# First of all, check if the user has set any of the PTHREAD_LIBS,
|
||||
# etcetera environment variables, and if threads linking works using
|
||||
# them:
|
||||
if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
|
||||
save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||
save_LIBS="$LIBS"
|
||||
LIBS="$PTHREAD_LIBS $LIBS"
|
||||
AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
|
||||
AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes)
|
||||
AC_MSG_RESULT($ax_pthread_ok)
|
||||
if test x"$ax_pthread_ok" = xno; then
|
||||
PTHREAD_LIBS=""
|
||||
PTHREAD_CFLAGS=""
|
||||
fi
|
||||
LIBS="$save_LIBS"
|
||||
CFLAGS="$save_CFLAGS"
|
||||
fi
|
||||
|
||||
# We must check for the threads library under a number of different
|
||||
# names; the ordering is very important because some systems
|
||||
# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
|
||||
# libraries is broken (non-POSIX).
|
||||
|
||||
# Create a list of thread flags to try. Items starting with a "-" are
|
||||
# C compiler flags, and other items are library names, except for "none"
|
||||
# which indicates that we try without any flags at all, and "pthread-config"
|
||||
# which is a program returning the flags for the Pth emulation library.
|
||||
|
||||
ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
|
||||
|
||||
# The ordering *is* (sometimes) important. Some notes on the
|
||||
# individual items follow:
|
||||
|
||||
# pthreads: AIX (must check this before -lpthread)
|
||||
# none: in case threads are in libc; should be tried before -Kthread and
|
||||
# other compiler flags to prevent continual compiler warnings
|
||||
# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
|
||||
# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
|
||||
# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
|
||||
# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
|
||||
# -pthreads: Solaris/gcc
|
||||
# -mthreads: Mingw32/gcc, Lynx/gcc
|
||||
# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
|
||||
# doesn't hurt to check since this sometimes defines pthreads too;
|
||||
# also defines -D_REENTRANT)
|
||||
# ... -mt is also the pthreads flag for HP/aCC
|
||||
# pthread: Linux, etcetera
|
||||
# --thread-safe: KAI C++
|
||||
# pthread-config: use pthread-config program (for GNU Pth library)
|
||||
|
||||
case "${host_cpu}-${host_os}" in
|
||||
*solaris*)
|
||||
|
||||
# On Solaris (at least, for some versions), libc contains stubbed
|
||||
# (non-functional) versions of the pthreads routines, so link-based
|
||||
# tests will erroneously succeed. (We need to link with -pthreads/-mt/
|
||||
# -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
|
||||
# a function called by this macro, so we could check for that, but
|
||||
# who knows whether they'll stub that too in a future libc.) So,
|
||||
# we'll just look for -pthreads and -lpthread first:
|
||||
|
||||
ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags"
|
||||
;;
|
||||
|
||||
*-darwin*)
|
||||
acx_pthread_flags="-pthread $acx_pthread_flags"
|
||||
;;
|
||||
esac
|
||||
|
||||
if test x"$ax_pthread_ok" = xno; then
|
||||
for flag in $ax_pthread_flags; do
|
||||
|
||||
case $flag in
|
||||
none)
|
||||
AC_MSG_CHECKING([whether pthreads work without any flags])
|
||||
;;
|
||||
|
||||
-*)
|
||||
AC_MSG_CHECKING([whether pthreads work with $flag])
|
||||
PTHREAD_CFLAGS="$flag"
|
||||
;;
|
||||
|
||||
pthread-config)
|
||||
AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no)
|
||||
if test x"$ax_pthread_config" = xno; then continue; fi
|
||||
PTHREAD_CFLAGS="`pthread-config --cflags`"
|
||||
PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
|
||||
;;
|
||||
|
||||
*)
|
||||
AC_MSG_CHECKING([for the pthreads library -l$flag])
|
||||
PTHREAD_LIBS="-l$flag"
|
||||
;;
|
||||
esac
|
||||
|
||||
save_LIBS="$LIBS"
|
||||
save_CFLAGS="$CFLAGS"
|
||||
LIBS="$PTHREAD_LIBS $LIBS"
|
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||
|
||||
# Check for various functions. We must include pthread.h,
|
||||
# since some functions may be macros. (On the Sequent, we
|
||||
# need a special flag -Kthread to make this header compile.)
|
||||
# We check for pthread_join because it is in -lpthread on IRIX
|
||||
# while pthread_create is in libc. We check for pthread_attr_init
|
||||
# due to DEC craziness with -lpthreads. We check for
|
||||
# pthread_cleanup_push because it is one of the few pthread
|
||||
# functions on Solaris that doesn't have a non-functional libc stub.
|
||||
# We try pthread_create on general principles.
|
||||
AC_TRY_LINK([#include <pthread.h>
|
||||
static void routine(void* a) {a=0;}
|
||||
static void* start_routine(void* a) {return a;}],
|
||||
[pthread_t th; pthread_attr_t attr;
|
||||
pthread_join(th, 0);
|
||||
pthread_attr_init(&attr);
|
||||
pthread_cleanup_push(routine, 0);
|
||||
pthread_create(&th,0,start_routine,0);
|
||||
pthread_cleanup_pop(0); ],
|
||||
[ax_pthread_ok=yes])
|
||||
|
||||
LIBS="$save_LIBS"
|
||||
CFLAGS="$save_CFLAGS"
|
||||
|
||||
AC_MSG_RESULT($ax_pthread_ok)
|
||||
if test "x$ax_pthread_ok" = xyes; then
|
||||
break;
|
||||
fi
|
||||
|
||||
PTHREAD_LIBS=""
|
||||
PTHREAD_CFLAGS=""
|
||||
done
|
||||
fi
|
||||
|
||||
# Various other checks:
|
||||
if test "x$ax_pthread_ok" = xyes; then
|
||||
save_LIBS="$LIBS"
|
||||
LIBS="$PTHREAD_LIBS $LIBS"
|
||||
save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
|
||||
|
||||
# Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
|
||||
AC_MSG_CHECKING([for joinable pthread attribute])
|
||||
attr_name=unknown
|
||||
for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
|
||||
AC_TRY_LINK([#include <pthread.h>], [int attr=$attr; return attr;],
|
||||
[attr_name=$attr; break])
|
||||
done
|
||||
AC_MSG_RESULT($attr_name)
|
||||
if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
|
||||
AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name,
|
||||
[Define to necessary symbol if this constant
|
||||
uses a non-standard name on your system.])
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([if more special flags are required for pthreads])
|
||||
flag=no
|
||||
case "${host_cpu}-${host_os}" in
|
||||
*-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";;
|
||||
*solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";;
|
||||
esac
|
||||
AC_MSG_RESULT(${flag})
|
||||
if test "x$flag" != xno; then
|
||||
PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
|
||||
fi
|
||||
|
||||
LIBS="$save_LIBS"
|
||||
CFLAGS="$save_CFLAGS"
|
||||
|
||||
# More AIX lossage: must compile with xlc_r or cc_r
|
||||
if test x"$GCC" != xyes; then
|
||||
AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC})
|
||||
else
|
||||
PTHREAD_CC=$CC
|
||||
fi
|
||||
else
|
||||
PTHREAD_CC="$CC"
|
||||
fi
|
||||
|
||||
AC_SUBST(PTHREAD_LIBS)
|
||||
AC_SUBST(PTHREAD_CFLAGS)
|
||||
AC_SUBST(PTHREAD_CC)
|
||||
|
||||
# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
|
||||
if test x"$ax_pthread_ok" = xyes; then
|
||||
ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1])
|
||||
:
|
||||
else
|
||||
ax_pthread_ok=no
|
||||
$2
|
||||
fi
|
||||
AC_LANG_RESTORE
|
||||
])dnl AX_PTHREAD
|
7377
m4/libtool.m4
vendored
Normal file
7377
m4/libtool.m4
vendored
Normal file
File diff suppressed because it is too large
Load Diff
368
m4/ltoptions.m4
vendored
Normal file
368
m4/ltoptions.m4
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
# Helper functions for option handling. -*- Autoconf -*-
|
||||
#
|
||||
# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
|
||||
# Written by Gary V. Vaughan, 2004
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 6 ltoptions.m4
|
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define.
|
||||
AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])])
|
||||
|
||||
|
||||
# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME)
|
||||
# ------------------------------------------
|
||||
m4_define([_LT_MANGLE_OPTION],
|
||||
[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])])
|
||||
|
||||
|
||||
# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME)
|
||||
# ---------------------------------------
|
||||
# Set option OPTION-NAME for macro MACRO-NAME, and if there is a
|
||||
# matching handler defined, dispatch to it. Other OPTION-NAMEs are
|
||||
# saved as a flag.
|
||||
m4_define([_LT_SET_OPTION],
|
||||
[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl
|
||||
m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]),
|
||||
_LT_MANGLE_DEFUN([$1], [$2]),
|
||||
[m4_warning([Unknown $1 option `$2'])])[]dnl
|
||||
])
|
||||
|
||||
|
||||
# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET])
|
||||
# ------------------------------------------------------------
|
||||
# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
|
||||
m4_define([_LT_IF_OPTION],
|
||||
[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])])
|
||||
|
||||
|
||||
# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET)
|
||||
# -------------------------------------------------------
|
||||
# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME
|
||||
# are set.
|
||||
m4_define([_LT_UNLESS_OPTIONS],
|
||||
[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
|
||||
[m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option),
|
||||
[m4_define([$0_found])])])[]dnl
|
||||
m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3
|
||||
])[]dnl
|
||||
])
|
||||
|
||||
|
||||
# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST)
|
||||
# ----------------------------------------
|
||||
# OPTION-LIST is a space-separated list of Libtool options associated
|
||||
# with MACRO-NAME. If any OPTION has a matching handler declared with
|
||||
# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about
|
||||
# the unknown option and exit.
|
||||
m4_defun([_LT_SET_OPTIONS],
|
||||
[# Set options
|
||||
m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
|
||||
[_LT_SET_OPTION([$1], _LT_Option)])
|
||||
|
||||
m4_if([$1],[LT_INIT],[
|
||||
dnl
|
||||
dnl Simply set some default values (i.e off) if boolean options were not
|
||||
dnl specified:
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no
|
||||
])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no
|
||||
])
|
||||
dnl
|
||||
dnl If no reference was made to various pairs of opposing options, then
|
||||
dnl we run the default mode handler for the pair. For example, if neither
|
||||
dnl `shared' nor `disable-shared' was passed, we enable building of shared
|
||||
dnl archives by default:
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install],
|
||||
[_LT_ENABLE_FAST_INSTALL])
|
||||
])
|
||||
])# _LT_SET_OPTIONS
|
||||
|
||||
|
||||
## --------------------------------- ##
|
||||
## Macros to handle LT_INIT options. ##
|
||||
## --------------------------------- ##
|
||||
|
||||
# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME)
|
||||
# -----------------------------------------
|
||||
m4_define([_LT_MANGLE_DEFUN],
|
||||
[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])])
|
||||
|
||||
|
||||
# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE)
|
||||
# -----------------------------------------------
|
||||
m4_define([LT_OPTION_DEFINE],
|
||||
[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl
|
||||
])# LT_OPTION_DEFINE
|
||||
|
||||
|
||||
# dlopen
|
||||
# ------
|
||||
LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes
|
||||
])
|
||||
|
||||
AU_DEFUN([AC_LIBTOOL_DLOPEN],
|
||||
[_LT_SET_OPTION([LT_INIT], [dlopen])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you
|
||||
put the `dlopen' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], [])
|
||||
|
||||
|
||||
# win32-dll
|
||||
# ---------
|
||||
# Declare package support for building win32 dll's.
|
||||
LT_OPTION_DEFINE([LT_INIT], [win32-dll],
|
||||
[enable_win32_dll=yes
|
||||
|
||||
case $host in
|
||||
*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-cegcc*)
|
||||
AC_CHECK_TOOL(AS, as, false)
|
||||
AC_CHECK_TOOL(DLLTOOL, dlltool, false)
|
||||
AC_CHECK_TOOL(OBJDUMP, objdump, false)
|
||||
;;
|
||||
esac
|
||||
|
||||
test -z "$AS" && AS=as
|
||||
_LT_DECL([], [AS], [0], [Assembler program])dnl
|
||||
|
||||
test -z "$DLLTOOL" && DLLTOOL=dlltool
|
||||
_LT_DECL([], [DLLTOOL], [0], [DLL creation program])dnl
|
||||
|
||||
test -z "$OBJDUMP" && OBJDUMP=objdump
|
||||
_LT_DECL([], [OBJDUMP], [0], [Object dumper program])dnl
|
||||
])# win32-dll
|
||||
|
||||
AU_DEFUN([AC_LIBTOOL_WIN32_DLL],
|
||||
[AC_REQUIRE([AC_CANONICAL_HOST])dnl
|
||||
_LT_SET_OPTION([LT_INIT], [win32-dll])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you
|
||||
put the `win32-dll' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [])
|
||||
|
||||
|
||||
# _LT_ENABLE_SHARED([DEFAULT])
|
||||
# ----------------------------
|
||||
# implement the --enable-shared flag, and supports the `shared' and
|
||||
# `disable-shared' LT_INIT options.
|
||||
# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'.
|
||||
m4_define([_LT_ENABLE_SHARED],
|
||||
[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl
|
||||
AC_ARG_ENABLE([shared],
|
||||
[AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@],
|
||||
[build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])],
|
||||
[p=${PACKAGE-default}
|
||||
case $enableval in
|
||||
yes) enable_shared=yes ;;
|
||||
no) enable_shared=no ;;
|
||||
*)
|
||||
enable_shared=no
|
||||
# Look at the argument we got. We use all the common list separators.
|
||||
lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
|
||||
for pkg in $enableval; do
|
||||
IFS="$lt_save_ifs"
|
||||
if test "X$pkg" = "X$p"; then
|
||||
enable_shared=yes
|
||||
fi
|
||||
done
|
||||
IFS="$lt_save_ifs"
|
||||
;;
|
||||
esac],
|
||||
[enable_shared=]_LT_ENABLE_SHARED_DEFAULT)
|
||||
|
||||
_LT_DECL([build_libtool_libs], [enable_shared], [0],
|
||||
[Whether or not to build shared libraries])
|
||||
])# _LT_ENABLE_SHARED
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])])
|
||||
|
||||
# Old names:
|
||||
AC_DEFUN([AC_ENABLE_SHARED],
|
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared])
|
||||
])
|
||||
|
||||
AC_DEFUN([AC_DISABLE_SHARED],
|
||||
[_LT_SET_OPTION([LT_INIT], [disable-shared])
|
||||
])
|
||||
|
||||
AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])
|
||||
AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AM_ENABLE_SHARED], [])
|
||||
dnl AC_DEFUN([AM_DISABLE_SHARED], [])
|
||||
|
||||
|
||||
|
||||
# _LT_ENABLE_STATIC([DEFAULT])
|
||||
# ----------------------------
|
||||
# implement the --enable-static flag, and support the `static' and
|
||||
# `disable-static' LT_INIT options.
|
||||
# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'.
|
||||
m4_define([_LT_ENABLE_STATIC],
|
||||
[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl
|
||||
AC_ARG_ENABLE([static],
|
||||
[AS_HELP_STRING([--enable-static@<:@=PKGS@:>@],
|
||||
[build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])],
|
||||
[p=${PACKAGE-default}
|
||||
case $enableval in
|
||||
yes) enable_static=yes ;;
|
||||
no) enable_static=no ;;
|
||||
*)
|
||||
enable_static=no
|
||||
# Look at the argument we got. We use all the common list separators.
|
||||
lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
|
||||
for pkg in $enableval; do
|
||||
IFS="$lt_save_ifs"
|
||||
if test "X$pkg" = "X$p"; then
|
||||
enable_static=yes
|
||||
fi
|
||||
done
|
||||
IFS="$lt_save_ifs"
|
||||
;;
|
||||
esac],
|
||||
[enable_static=]_LT_ENABLE_STATIC_DEFAULT)
|
||||
|
||||
_LT_DECL([build_old_libs], [enable_static], [0],
|
||||
[Whether or not to build static libraries])
|
||||
])# _LT_ENABLE_STATIC
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])])
|
||||
|
||||
# Old names:
|
||||
AC_DEFUN([AC_ENABLE_STATIC],
|
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static])
|
||||
])
|
||||
|
||||
AC_DEFUN([AC_DISABLE_STATIC],
|
||||
[_LT_SET_OPTION([LT_INIT], [disable-static])
|
||||
])
|
||||
|
||||
AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])
|
||||
AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AM_ENABLE_STATIC], [])
|
||||
dnl AC_DEFUN([AM_DISABLE_STATIC], [])
|
||||
|
||||
|
||||
|
||||
# _LT_ENABLE_FAST_INSTALL([DEFAULT])
|
||||
# ----------------------------------
|
||||
# implement the --enable-fast-install flag, and support the `fast-install'
|
||||
# and `disable-fast-install' LT_INIT options.
|
||||
# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'.
|
||||
m4_define([_LT_ENABLE_FAST_INSTALL],
|
||||
[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl
|
||||
AC_ARG_ENABLE([fast-install],
|
||||
[AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@],
|
||||
[optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])],
|
||||
[p=${PACKAGE-default}
|
||||
case $enableval in
|
||||
yes) enable_fast_install=yes ;;
|
||||
no) enable_fast_install=no ;;
|
||||
*)
|
||||
enable_fast_install=no
|
||||
# Look at the argument we got. We use all the common list separators.
|
||||
lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
|
||||
for pkg in $enableval; do
|
||||
IFS="$lt_save_ifs"
|
||||
if test "X$pkg" = "X$p"; then
|
||||
enable_fast_install=yes
|
||||
fi
|
||||
done
|
||||
IFS="$lt_save_ifs"
|
||||
;;
|
||||
esac],
|
||||
[enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT)
|
||||
|
||||
_LT_DECL([fast_install], [enable_fast_install], [0],
|
||||
[Whether or not to optimize for fast installation])dnl
|
||||
])# _LT_ENABLE_FAST_INSTALL
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])])
|
||||
|
||||
# Old names:
|
||||
AU_DEFUN([AC_ENABLE_FAST_INSTALL],
|
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you put
|
||||
the `fast-install' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
AU_DEFUN([AC_DISABLE_FAST_INSTALL],
|
||||
[_LT_SET_OPTION([LT_INIT], [disable-fast-install])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you put
|
||||
the `disable-fast-install' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], [])
|
||||
dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], [])
|
||||
|
||||
|
||||
# _LT_WITH_PIC([MODE])
|
||||
# --------------------
|
||||
# implement the --with-pic flag, and support the `pic-only' and `no-pic'
|
||||
# LT_INIT options.
|
||||
# MODE is either `yes' or `no'. If omitted, it defaults to `both'.
|
||||
m4_define([_LT_WITH_PIC],
|
||||
[AC_ARG_WITH([pic],
|
||||
[AS_HELP_STRING([--with-pic],
|
||||
[try to use only PIC/non-PIC objects @<:@default=use both@:>@])],
|
||||
[pic_mode="$withval"],
|
||||
[pic_mode=default])
|
||||
|
||||
test -z "$pic_mode" && pic_mode=m4_default([$1], [default])
|
||||
|
||||
_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl
|
||||
])# _LT_WITH_PIC
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])])
|
||||
|
||||
# Old name:
|
||||
AU_DEFUN([AC_LIBTOOL_PICMODE],
|
||||
[_LT_SET_OPTION([LT_INIT], [pic-only])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you
|
||||
put the `pic-only' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_LIBTOOL_PICMODE], [])
|
||||
|
||||
## ----------------- ##
|
||||
## LTDL_INIT Options ##
|
||||
## ----------------- ##
|
||||
|
||||
m4_define([_LTDL_MODE], [])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive],
|
||||
[m4_define([_LTDL_MODE], [nonrecursive])])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [recursive],
|
||||
[m4_define([_LTDL_MODE], [recursive])])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [subproject],
|
||||
[m4_define([_LTDL_MODE], [subproject])])
|
||||
|
||||
m4_define([_LTDL_TYPE], [])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [installable],
|
||||
[m4_define([_LTDL_TYPE], [installable])])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [convenience],
|
||||
[m4_define([_LTDL_TYPE], [convenience])])
|
123
m4/ltsugar.m4
vendored
Normal file
123
m4/ltsugar.m4
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*-
|
||||
#
|
||||
# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
|
||||
# Written by Gary V. Vaughan, 2004
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 6 ltsugar.m4
|
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define.
|
||||
AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])])
|
||||
|
||||
|
||||
# lt_join(SEP, ARG1, [ARG2...])
|
||||
# -----------------------------
|
||||
# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their
|
||||
# associated separator.
|
||||
# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier
|
||||
# versions in m4sugar had bugs.
|
||||
m4_define([lt_join],
|
||||
[m4_if([$#], [1], [],
|
||||
[$#], [2], [[$2]],
|
||||
[m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])])
|
||||
m4_define([_lt_join],
|
||||
[m4_if([$#$2], [2], [],
|
||||
[m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])])
|
||||
|
||||
|
||||
# lt_car(LIST)
|
||||
# lt_cdr(LIST)
|
||||
# ------------
|
||||
# Manipulate m4 lists.
|
||||
# These macros are necessary as long as will still need to support
|
||||
# Autoconf-2.59 which quotes differently.
|
||||
m4_define([lt_car], [[$1]])
|
||||
m4_define([lt_cdr],
|
||||
[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])],
|
||||
[$#], 1, [],
|
||||
[m4_dquote(m4_shift($@))])])
|
||||
m4_define([lt_unquote], $1)
|
||||
|
||||
|
||||
# lt_append(MACRO-NAME, STRING, [SEPARATOR])
|
||||
# ------------------------------------------
|
||||
# Redefine MACRO-NAME to hold its former content plus `SEPARATOR'`STRING'.
|
||||
# Note that neither SEPARATOR nor STRING are expanded; they are appended
|
||||
# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked).
|
||||
# No SEPARATOR is output if MACRO-NAME was previously undefined (different
|
||||
# than defined and empty).
|
||||
#
|
||||
# This macro is needed until we can rely on Autoconf 2.62, since earlier
|
||||
# versions of m4sugar mistakenly expanded SEPARATOR but not STRING.
|
||||
m4_define([lt_append],
|
||||
[m4_define([$1],
|
||||
m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])])
|
||||
|
||||
|
||||
|
||||
# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...])
|
||||
# ----------------------------------------------------------
|
||||
# Produce a SEP delimited list of all paired combinations of elements of
|
||||
# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list
|
||||
# has the form PREFIXmINFIXSUFFIXn.
|
||||
# Needed until we can rely on m4_combine added in Autoconf 2.62.
|
||||
m4_define([lt_combine],
|
||||
[m4_if(m4_eval([$# > 3]), [1],
|
||||
[m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl
|
||||
[[m4_foreach([_Lt_prefix], [$2],
|
||||
[m4_foreach([_Lt_suffix],
|
||||
]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[,
|
||||
[_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])])
|
||||
|
||||
|
||||
# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ])
|
||||
# -----------------------------------------------------------------------
|
||||
# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited
|
||||
# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ.
|
||||
m4_define([lt_if_append_uniq],
|
||||
[m4_ifdef([$1],
|
||||
[m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1],
|
||||
[lt_append([$1], [$2], [$3])$4],
|
||||
[$5])],
|
||||
[lt_append([$1], [$2], [$3])$4])])
|
||||
|
||||
|
||||
# lt_dict_add(DICT, KEY, VALUE)
|
||||
# -----------------------------
|
||||
m4_define([lt_dict_add],
|
||||
[m4_define([$1($2)], [$3])])
|
||||
|
||||
|
||||
# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE)
|
||||
# --------------------------------------------
|
||||
m4_define([lt_dict_add_subkey],
|
||||
[m4_define([$1($2:$3)], [$4])])
|
||||
|
||||
|
||||
# lt_dict_fetch(DICT, KEY, [SUBKEY])
|
||||
# ----------------------------------
|
||||
m4_define([lt_dict_fetch],
|
||||
[m4_ifval([$3],
|
||||
m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]),
|
||||
m4_ifdef([$1($2)], [m4_defn([$1($2)])]))])
|
||||
|
||||
|
||||
# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE])
|
||||
# -----------------------------------------------------------------
|
||||
m4_define([lt_if_dict_fetch],
|
||||
[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4],
|
||||
[$5],
|
||||
[$6])])
|
||||
|
||||
|
||||
# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...])
|
||||
# --------------------------------------------------------------
|
||||
m4_define([lt_dict_filter],
|
||||
[m4_if([$5], [], [],
|
||||
[lt_join(m4_quote(m4_default([$4], [[, ]])),
|
||||
lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]),
|
||||
[lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl
|
||||
])
|
23
m4/ltversion.m4
vendored
Normal file
23
m4/ltversion.m4
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# ltversion.m4 -- version numbers -*- Autoconf -*-
|
||||
#
|
||||
# Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
# Written by Scott James Remnant, 2004
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# Generated from ltversion.in.
|
||||
|
||||
# serial 3017 ltversion.m4
|
||||
# This file is part of GNU Libtool
|
||||
|
||||
m4_define([LT_PACKAGE_VERSION], [2.2.6b])
|
||||
m4_define([LT_PACKAGE_REVISION], [1.3017])
|
||||
|
||||
AC_DEFUN([LTVERSION_VERSION],
|
||||
[macro_version='2.2.6b'
|
||||
macro_revision='1.3017'
|
||||
_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?])
|
||||
_LT_DECL(, macro_revision, 0)
|
||||
])
|
92
m4/lt~obsolete.m4
vendored
Normal file
92
m4/lt~obsolete.m4
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*-
|
||||
#
|
||||
# Copyright (C) 2004, 2005, 2007 Free Software Foundation, Inc.
|
||||
# Written by Scott James Remnant, 2004.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 4 lt~obsolete.m4
|
||||
|
||||
# These exist entirely to fool aclocal when bootstrapping libtool.
|
||||
#
|
||||
# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN)
|
||||
# which have later been changed to m4_define as they aren't part of the
|
||||
# exported API, or moved to Autoconf or Automake where they belong.
|
||||
#
|
||||
# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN
|
||||
# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us
|
||||
# using a macro with the same name in our local m4/libtool.m4 it'll
|
||||
# pull the old libtool.m4 in (it doesn't see our shiny new m4_define
|
||||
# and doesn't know about Autoconf macros at all.)
|
||||
#
|
||||
# So we provide this file, which has a silly filename so it's always
|
||||
# included after everything else. This provides aclocal with the
|
||||
# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything
|
||||
# because those macros already exist, or will be overwritten later.
|
||||
# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6.
|
||||
#
|
||||
# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.
|
||||
# Yes, that means every name once taken will need to remain here until
|
||||
# we give up compatibility with versions before 1.7, at which point
|
||||
# we need to keep only those names which we still refer to.
|
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define.
|
||||
AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])
|
||||
|
||||
m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])
|
||||
m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])])
|
||||
m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])
|
||||
m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])])
|
||||
m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])
|
||||
m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])])
|
||||
m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])])
|
||||
m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])
|
||||
m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])])
|
||||
m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])])
|
||||
m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])
|
||||
m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])
|
||||
m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])])
|
||||
m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])
|
||||
m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])])
|
||||
m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])])
|
||||
m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])
|
||||
m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])
|
||||
m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])
|
||||
m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])])
|
||||
m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])])
|
||||
m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])])
|
||||
m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])])
|
||||
m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])])
|
||||
m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])])
|
||||
m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])])
|
||||
m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])
|
||||
m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])])
|
||||
m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])])
|
||||
m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])])
|
||||
m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])])
|
||||
m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])])
|
||||
m4_ifndef([AC_LIBTOOL_RC], [AC_DEFUN([AC_LIBTOOL_RC])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])])
|
||||
m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])])
|
44
src/client/linux/android_link.h
Normal file
44
src/client/linux/android_link.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2010, 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.
|
||||
|
||||
// The Android NDK doesn't have link.h. Fortunately, the only thing
|
||||
// that Breakpad uses from it is the ElfW macro, so define it here.
|
||||
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
|
||||
#define GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
|
||||
|
||||
#include <sys/exec_elf.h>
|
||||
|
||||
#ifndef ElfW
|
||||
#define ElfW(type) _ElfW (Elf, ELFSIZE, type)
|
||||
#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t)
|
||||
#define _ElfW_1(e,w,t) e##w##t
|
||||
#endif
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
|
77
src/client/linux/android_ucontext.h
Normal file
77
src/client/linux/android_ucontext.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2009, 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.
|
||||
|
||||
// Android runs a fairly new Linux kernel, so signal info is there,
|
||||
// but the NDK doesn't have the structs defined, so define
|
||||
// them here.
|
||||
// Adapted from platform-linux.cc in V8
|
||||
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_
|
||||
#define GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#if !defined(__GLIBC__) && (defined(__arm__) || defined(__thumb__))
|
||||
|
||||
struct sigcontext {
|
||||
uint32_t trap_no;
|
||||
uint32_t error_code;
|
||||
uint32_t oldmask;
|
||||
uint32_t arm_r0;
|
||||
uint32_t arm_r1;
|
||||
uint32_t arm_r2;
|
||||
uint32_t arm_r3;
|
||||
uint32_t arm_r4;
|
||||
uint32_t arm_r5;
|
||||
uint32_t arm_r6;
|
||||
uint32_t arm_r7;
|
||||
uint32_t arm_r8;
|
||||
uint32_t arm_r9;
|
||||
uint32_t arm_r10;
|
||||
uint32_t arm_fp;
|
||||
uint32_t arm_ip;
|
||||
uint32_t arm_sp;
|
||||
uint32_t arm_lr;
|
||||
uint32_t arm_pc;
|
||||
uint32_t arm_cpsr;
|
||||
uint32_t fault_address;
|
||||
};
|
||||
typedef uint32_t __sigset_t;
|
||||
typedef struct sigcontext mcontext_t;
|
||||
typedef struct ucontext {
|
||||
uint32_t uc_flags;
|
||||
struct ucontext* uc_link;
|
||||
stack_t uc_stack;
|
||||
mcontext_t uc_mcontext;
|
||||
__sigset_t uc_sigmask;
|
||||
} ucontext_t;
|
||||
|
||||
#endif
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_UCONTEXT_H_
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -27,11 +27,18 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// precompile.cpp : source file that includes just the standard includes
|
||||
// CrashGenerationApp.pch will be the pre-compiled header
|
||||
// precompile.obj will contain the pre-compiled type information
|
||||
#ifndef CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
|
||||
#define CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
|
||||
|
||||
#include "precompile.h"
|
||||
namespace google_breakpad {
|
||||
|
||||
// Reference any additional headers you need in PRECOMPILE.H
|
||||
// and not in this file.
|
||||
class CrashGenerationServer;
|
||||
|
||||
struct ClientInfo {
|
||||
CrashGenerationServer* crash_server_;
|
||||
pid_t pid_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_
|
89
src/client/linux/crash_generation/crash_generation_client.cc
Normal file
89
src/client/linux/crash_generation/crash_generation_client.cc
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2010 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 <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "client/linux/crash_generation/crash_generation_client.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
bool
|
||||
CrashGenerationClient::RequestDump(const void* blob, size_t blob_size)
|
||||
{
|
||||
int fds[2];
|
||||
sys_socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
||||
static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int));
|
||||
|
||||
struct kernel_msghdr msg;
|
||||
my_memset(&msg, 0, sizeof(struct kernel_msghdr));
|
||||
struct kernel_iovec iov[1];
|
||||
iov[0].iov_base = const_cast<void*>(blob);
|
||||
iov[0].iov_len = blob_size;
|
||||
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
|
||||
char cmsg[kControlMsgSize];
|
||||
my_memset(cmsg, 0, kControlMsgSize);
|
||||
msg.msg_control = cmsg;
|
||||
msg.msg_controllen = sizeof(cmsg);
|
||||
|
||||
struct cmsghdr* hdr = CMSG_FIRSTHDR(&msg);
|
||||
hdr->cmsg_level = SOL_SOCKET;
|
||||
hdr->cmsg_type = SCM_RIGHTS;
|
||||
hdr->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
int* p = reinterpret_cast<int*>(CMSG_DATA(hdr));
|
||||
*p = fds[1];
|
||||
|
||||
HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0));
|
||||
sys_close(fds[1]);
|
||||
|
||||
// wait for an ACK from the server
|
||||
char b;
|
||||
HANDLE_EINTR(sys_read(fds[0], &b, 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//static
|
||||
CrashGenerationClient*
|
||||
CrashGenerationClient::TryCreate(int server_fd)
|
||||
{
|
||||
if (0 > server_fd)
|
||||
return NULL;
|
||||
return new CrashGenerationClient(server_fd);
|
||||
}
|
||||
|
||||
}
|
69
src/client/linux/crash_generation/crash_generation_client.h
Normal file
69
src/client/linux/crash_generation/crash_generation_client.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2010 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 CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
||||
#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class CrashGenerationClient {
|
||||
public:
|
||||
~CrashGenerationClient()
|
||||
{
|
||||
}
|
||||
|
||||
// Request the crash server to generate a dump. |blob| is a hack,
|
||||
// see exception_handler.h and minidump_writer.h
|
||||
//
|
||||
// Return true if the dump was successful; false otherwise.
|
||||
bool RequestDump(const void* blob, size_t blob_size);
|
||||
|
||||
// Return a new CrashGenerationClient if |server_fd| is valid and
|
||||
// connects to a CrashGenerationServer. Otherwise, return NULL.
|
||||
// The returned CrashGenerationClient* is owned by the caller of
|
||||
// this function.
|
||||
static CrashGenerationClient* TryCreate(int server_fd);
|
||||
|
||||
private:
|
||||
CrashGenerationClient(int server_fd) : server_fd_(server_fd)
|
||||
{
|
||||
}
|
||||
|
||||
int server_fd_;
|
||||
|
||||
// prevent copy construction and assignment
|
||||
CrashGenerationClient(const CrashGenerationClient&);
|
||||
CrashGenerationClient& operator=(const CrashGenerationClient&);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
468
src/client/linux/crash_generation/crash_generation_server.cc
Normal file
468
src/client/linux/crash_generation/crash_generation_server.cc
Normal file
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2010 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 <assert.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "client/linux/crash_generation/crash_generation_server.h"
|
||||
#include "client/linux/crash_generation/client_info.h"
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
|
||||
static const char kCommandQuit = 'x';
|
||||
|
||||
static bool
|
||||
GetInodeForFileDescriptor(ino_t* inode_out, int fd)
|
||||
{
|
||||
assert(inode_out);
|
||||
|
||||
struct stat buf;
|
||||
if (fstat(fd, &buf) < 0)
|
||||
return false;
|
||||
|
||||
if (!S_ISSOCK(buf.st_mode))
|
||||
return false;
|
||||
|
||||
*inode_out = buf.st_ino;
|
||||
return true;
|
||||
}
|
||||
|
||||
// expected prefix of the target of the /proc/self/fd/%d link for a socket
|
||||
static const char kSocketLinkPrefix[] = "socket:[";
|
||||
|
||||
// Parse a symlink in /proc/pid/fd/$x and return the inode number of the
|
||||
// socket.
|
||||
// inode_out: (output) set to the inode number on success
|
||||
// path: e.g. /proc/1234/fd/5 (must be a UNIX domain socket descriptor)
|
||||
static bool
|
||||
GetInodeForProcPath(ino_t* inode_out, const char* path)
|
||||
{
|
||||
assert(inode_out);
|
||||
assert(path);
|
||||
|
||||
char buf[256];
|
||||
const ssize_t n = readlink(path, buf, sizeof(buf) - 1);
|
||||
if (n == -1) {
|
||||
return false;
|
||||
}
|
||||
buf[n] = 0;
|
||||
|
||||
if (0 != memcmp(kSocketLinkPrefix, buf, sizeof(kSocketLinkPrefix) - 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char* endptr;
|
||||
const u_int64_t inode_ul =
|
||||
strtoull(buf + sizeof(kSocketLinkPrefix) - 1, &endptr, 10);
|
||||
if (*endptr != ']')
|
||||
return false;
|
||||
|
||||
if (inode_ul == ULLONG_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*inode_out = inode_ul;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode)
|
||||
{
|
||||
assert(pid_out);
|
||||
bool already_found = false;
|
||||
|
||||
DIR* proc = opendir("/proc");
|
||||
if (!proc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<pid_t> pids;
|
||||
|
||||
struct dirent* dent;
|
||||
while ((dent = readdir(proc))) {
|
||||
char* endptr;
|
||||
const unsigned long int pid_ul = strtoul(dent->d_name, &endptr, 10);
|
||||
if (pid_ul == ULONG_MAX || '\0' != *endptr)
|
||||
continue;
|
||||
pids.push_back(pid_ul);
|
||||
}
|
||||
closedir(proc);
|
||||
|
||||
for (std::vector<pid_t>::const_iterator
|
||||
i = pids.begin(); i != pids.end(); ++i) {
|
||||
const pid_t current_pid = *i;
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "/proc/%d/fd", current_pid);
|
||||
DIR* fd = opendir(buf);
|
||||
if (!fd)
|
||||
continue;
|
||||
|
||||
while ((dent = readdir(fd))) {
|
||||
if (snprintf(buf, sizeof(buf), "/proc/%d/fd/%s", current_pid,
|
||||
dent->d_name) >= static_cast<int>(sizeof(buf))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ino_t fd_inode;
|
||||
if (GetInodeForProcPath(&fd_inode, buf)
|
||||
&& fd_inode == socket_inode) {
|
||||
if (already_found) {
|
||||
closedir(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
already_found = true;
|
||||
*pid_out = current_pid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(fd);
|
||||
}
|
||||
|
||||
return already_found;
|
||||
}
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
CrashGenerationServer::CrashGenerationServer(
|
||||
const int listen_fd,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void* dump_context,
|
||||
OnClientExitingCallback exit_callback,
|
||||
void* exit_context,
|
||||
bool generate_dumps,
|
||||
const std::string* dump_path) :
|
||||
server_fd_(listen_fd),
|
||||
dump_callback_(dump_callback),
|
||||
dump_context_(dump_context),
|
||||
exit_callback_(exit_callback),
|
||||
exit_context_(exit_context),
|
||||
generate_dumps_(generate_dumps),
|
||||
started_(false)
|
||||
{
|
||||
if (dump_path)
|
||||
dump_dir_ = *dump_path;
|
||||
else
|
||||
dump_dir_ = "/tmp";
|
||||
}
|
||||
|
||||
CrashGenerationServer::~CrashGenerationServer()
|
||||
{
|
||||
if (started_)
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool
|
||||
CrashGenerationServer::Start()
|
||||
{
|
||||
if (started_ || 0 > server_fd_)
|
||||
return false;
|
||||
|
||||
int control_pipe[2];
|
||||
if (pipe(control_pipe))
|
||||
return false;
|
||||
|
||||
if (fcntl(control_pipe[0], F_SETFD, FD_CLOEXEC))
|
||||
return false;
|
||||
if (fcntl(control_pipe[1], F_SETFD, FD_CLOEXEC))
|
||||
return false;
|
||||
|
||||
if (fcntl(control_pipe[0], F_SETFL, O_NONBLOCK))
|
||||
return false;
|
||||
|
||||
control_pipe_in_ = control_pipe[0];
|
||||
control_pipe_out_ = control_pipe[1];
|
||||
|
||||
if (pthread_create(&thread_, NULL,
|
||||
ThreadMain, reinterpret_cast<void*>(this)))
|
||||
return false;
|
||||
|
||||
started_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
CrashGenerationServer::Stop()
|
||||
{
|
||||
assert(pthread_self() != thread_);
|
||||
|
||||
if (!started_)
|
||||
return;
|
||||
|
||||
HANDLE_EINTR(write(control_pipe_out_, &kCommandQuit, 1));
|
||||
|
||||
void* dummy;
|
||||
pthread_join(thread_, &dummy);
|
||||
|
||||
started_ = false;
|
||||
}
|
||||
|
||||
//static
|
||||
bool
|
||||
CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd)
|
||||
{
|
||||
int fds[2];
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds))
|
||||
return false;
|
||||
|
||||
static const int on = 1;
|
||||
// Enable passcred on the server end of the socket
|
||||
if (setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)))
|
||||
return false;
|
||||
|
||||
if (fcntl(fds[1], F_SETFL, O_NONBLOCK))
|
||||
return false;
|
||||
if (fcntl(fds[1], F_SETFD, FD_CLOEXEC))
|
||||
return false;
|
||||
|
||||
*client_fd = fds[0];
|
||||
*server_fd = fds[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
// The following methods/functions execute on the server thread
|
||||
|
||||
void
|
||||
CrashGenerationServer::Run()
|
||||
{
|
||||
struct pollfd pollfds[2];
|
||||
memset(&pollfds, 0, sizeof(pollfds));
|
||||
|
||||
pollfds[0].fd = server_fd_;
|
||||
pollfds[0].events = POLLIN;
|
||||
|
||||
pollfds[1].fd = control_pipe_in_;
|
||||
pollfds[1].events = POLLIN;
|
||||
|
||||
while (true) {
|
||||
// infinite timeout
|
||||
int nevents = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1);
|
||||
if (-1 == nevents) {
|
||||
if (EINTR == errno) {
|
||||
continue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pollfds[0].revents && !ClientEvent(pollfds[0].revents))
|
||||
return;
|
||||
|
||||
if (pollfds[1].revents && !ControlEvent(pollfds[1].revents))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
CrashGenerationServer::ClientEvent(short revents)
|
||||
{
|
||||
if (POLLHUP & revents)
|
||||
return false;
|
||||
assert(POLLIN & revents);
|
||||
|
||||
// A process has crashed and has signaled us by writing a datagram
|
||||
// to the death signal socket. The datagram contains the crash context needed
|
||||
// for writing the minidump as well as a file descriptor and a credentials
|
||||
// block so that they can't lie about their pid.
|
||||
|
||||
// The length of the control message:
|
||||
static const unsigned kControlMsgSize =
|
||||
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
|
||||
// The length of the regular payload:
|
||||
static const unsigned kCrashContextSize =
|
||||
sizeof(google_breakpad::ExceptionHandler::CrashContext);
|
||||
|
||||
struct msghdr msg = {0};
|
||||
struct iovec iov[1];
|
||||
char crash_context[kCrashContextSize];
|
||||
char control[kControlMsgSize];
|
||||
const ssize_t expected_msg_size = sizeof(crash_context);
|
||||
|
||||
iov[0].iov_base = crash_context;
|
||||
iov[0].iov_len = sizeof(crash_context);
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]);
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = kControlMsgSize;
|
||||
|
||||
const ssize_t msg_size = HANDLE_EINTR(recvmsg(server_fd_, &msg, 0));
|
||||
if (msg_size != expected_msg_size)
|
||||
return true;
|
||||
|
||||
if (msg.msg_controllen != kControlMsgSize ||
|
||||
msg.msg_flags & ~MSG_TRUNC)
|
||||
return true;
|
||||
|
||||
// Walk the control payload and extract the file descriptor and validated pid.
|
||||
pid_t crashing_pid = -1;
|
||||
int signal_fd = -1;
|
||||
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr;
|
||||
hdr = CMSG_NXTHDR(&msg, hdr)) {
|
||||
if (hdr->cmsg_level != SOL_SOCKET)
|
||||
continue;
|
||||
if (hdr->cmsg_type == SCM_RIGHTS) {
|
||||
const unsigned len = hdr->cmsg_len -
|
||||
(((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr);
|
||||
assert(len % sizeof(int) == 0u);
|
||||
const unsigned num_fds = len / sizeof(int);
|
||||
if (num_fds > 1 || num_fds == 0) {
|
||||
// A nasty process could try and send us too many descriptors and
|
||||
// force a leak.
|
||||
for (unsigned i = 0; i < num_fds; ++i)
|
||||
HANDLE_EINTR(close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i]));
|
||||
return true;
|
||||
} else {
|
||||
signal_fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0];
|
||||
}
|
||||
} else if (hdr->cmsg_type == SCM_CREDENTIALS) {
|
||||
const struct ucred *cred =
|
||||
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
|
||||
crashing_pid = cred->pid;
|
||||
}
|
||||
}
|
||||
|
||||
if (crashing_pid == -1 || signal_fd == -1) {
|
||||
if (signal_fd)
|
||||
HANDLE_EINTR(close(signal_fd));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Kernel bug workaround (broken in 2.6.30 at least):
|
||||
// The kernel doesn't translate PIDs in SCM_CREDENTIALS across PID
|
||||
// namespaces. Thus |crashing_pid| might be garbage from our point of view.
|
||||
// In the future we can remove this workaround, but we have to wait a couple
|
||||
// of years to be sure that it's worked its way out into the world.
|
||||
|
||||
ino_t inode_number;
|
||||
if (!GetInodeForFileDescriptor(&inode_number, signal_fd)) {
|
||||
HANDLE_EINTR(close(signal_fd));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!FindProcessHoldingSocket(&crashing_pid, inode_number - 1)) {
|
||||
HANDLE_EINTR(close(signal_fd));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string minidump_filename;
|
||||
if (!MakeMinidumpFilename(minidump_filename))
|
||||
return true;
|
||||
|
||||
if (!google_breakpad::WriteMinidump(minidump_filename.c_str(),
|
||||
crashing_pid, crash_context,
|
||||
kCrashContextSize)) {
|
||||
HANDLE_EINTR(close(signal_fd));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dump_callback_) {
|
||||
ClientInfo info;
|
||||
|
||||
info.crash_server_ = this;
|
||||
info.pid_ = crashing_pid;
|
||||
|
||||
dump_callback_(dump_context_, &info, &minidump_filename);
|
||||
}
|
||||
|
||||
// Send the done signal to the process: it can exit now.
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
struct iovec done_iov;
|
||||
done_iov.iov_base = const_cast<char*>("\x42");
|
||||
done_iov.iov_len = 1;
|
||||
msg.msg_iov = &done_iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
HANDLE_EINTR(sendmsg(signal_fd, &msg, MSG_DONTWAIT | MSG_NOSIGNAL));
|
||||
HANDLE_EINTR(close(signal_fd));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CrashGenerationServer::ControlEvent(short revents)
|
||||
{
|
||||
if (POLLHUP & revents)
|
||||
return false;
|
||||
assert(POLLIN & revents);
|
||||
|
||||
char command;
|
||||
if (read(control_pipe_in_, &command, 1))
|
||||
return false;
|
||||
|
||||
switch (command) {
|
||||
case kCommandQuit:
|
||||
return false;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CrashGenerationServer::MakeMinidumpFilename(std::string& outFilename)
|
||||
{
|
||||
GUID guid;
|
||||
char guidString[kGUIDStringLength+1];
|
||||
|
||||
if (!(CreateGUID(&guid)
|
||||
&& GUIDToString(&guid, guidString, sizeof(guidString))))
|
||||
return false;
|
||||
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString);
|
||||
|
||||
outFilename = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
void*
|
||||
CrashGenerationServer::ThreadMain(void *arg)
|
||||
{
|
||||
reinterpret_cast<CrashGenerationServer*>(arg)->Run();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
133
src/client/linux/crash_generation/crash_generation_server.h
Normal file
133
src/client/linux/crash_generation/crash_generation_server.h
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2010 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 CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
||||
#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class ClientInfo;
|
||||
|
||||
class CrashGenerationServer {
|
||||
public:
|
||||
// WARNING: callbacks may be invoked on a different thread
|
||||
// than that which creates the CrashGenerationServer. They must
|
||||
// be thread safe.
|
||||
typedef void (*OnClientDumpRequestCallback)(void* context,
|
||||
const ClientInfo* client_info,
|
||||
const std::string* file_path);
|
||||
|
||||
typedef void (*OnClientExitingCallback)(void* context,
|
||||
const ClientInfo* client_info);
|
||||
|
||||
// Create an instance with the given parameters.
|
||||
//
|
||||
// Parameter listen_fd: The server fd created by CreateReportChannel().
|
||||
// Parameter dump_callback: Callback for a client crash dump request.
|
||||
// Parameter dump_context: Context for client crash dump request callback.
|
||||
// Parameter exit_callback: Callback for client process exit.
|
||||
// Parameter exit_context: Context for client exit callback.
|
||||
// Parameter generate_dumps: Whether to automatically generate dumps.
|
||||
// Client code of this class might want to generate dumps explicitly
|
||||
// in the crash dump request callback. In that case, false can be
|
||||
// passed for this parameter.
|
||||
// Parameter dump_path: Path for generating dumps; required only if true is
|
||||
// passed for generateDumps parameter; NULL can be passed otherwise.
|
||||
CrashGenerationServer(const int listen_fd,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void* dump_context,
|
||||
OnClientExitingCallback exit_callback,
|
||||
void* exit_context,
|
||||
bool generate_dumps,
|
||||
const std::string* dump_path);
|
||||
|
||||
~CrashGenerationServer();
|
||||
|
||||
// Perform initialization steps needed to start listening to clients.
|
||||
//
|
||||
// Return true if initialization is successful; false otherwise.
|
||||
bool Start();
|
||||
|
||||
// Stop the server.
|
||||
void Stop();
|
||||
|
||||
// Create a "channel" that can be used by clients to report crashes
|
||||
// to a CrashGenerationServer. |*server_fd| should be passed to
|
||||
// this class's constructor, and |*client_fd| should be passed to
|
||||
// the ExceptionHandler constructor in the client process.
|
||||
static bool CreateReportChannel(int* server_fd, int* client_fd);
|
||||
|
||||
private:
|
||||
// Run the server's event loop
|
||||
void Run();
|
||||
|
||||
// Invoked when an child process (client) event occurs
|
||||
// Returning true => "keep running", false => "exit loop"
|
||||
bool ClientEvent(short revents);
|
||||
|
||||
// Invoked when the controlling thread (main) event occurs
|
||||
// Returning true => "keep running", false => "exit loop"
|
||||
bool ControlEvent(short revents);
|
||||
|
||||
// Return a unique filename at which a minidump can be written
|
||||
bool MakeMinidumpFilename(std::string& outFilename);
|
||||
|
||||
// Trampoline to |Run()|
|
||||
static void* ThreadMain(void* arg);
|
||||
|
||||
int server_fd_;
|
||||
|
||||
OnClientDumpRequestCallback dump_callback_;
|
||||
void* dump_context_;
|
||||
|
||||
OnClientExitingCallback exit_callback_;
|
||||
void* exit_context_;
|
||||
|
||||
bool generate_dumps_;
|
||||
|
||||
std::string dump_dir_;
|
||||
|
||||
bool started_;
|
||||
|
||||
pthread_t thread_;
|
||||
int control_pipe_in_;
|
||||
int control_pipe_out_;
|
||||
|
||||
// disable these
|
||||
CrashGenerationServer(const CrashGenerationServer&);
|
||||
CrashGenerationServer& operator=(const CrashGenerationServer&);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
@@ -1,45 +0,0 @@
|
||||
CXX=g++
|
||||
CC=gcc
|
||||
|
||||
CXXFLAGS=-gstabs+ -I../../../ -I../../../testing/gtest/include -I../../../testing/include -I../../../testing/gtest -D_REENTRANT -m32
|
||||
CFLAGS=$(CXXFLAGS)
|
||||
LDFLAGS=-lpthread
|
||||
|
||||
OBJ_DIR=.
|
||||
BIN_DIR=.
|
||||
|
||||
TEST_CC_SRC=exception_handler_unittest.cc \
|
||||
exception_handler.cc \
|
||||
../../../testing/gtest/src/gtest-all.cc \
|
||||
../../../common/linux/guid_creator.cc \
|
||||
../minidump_writer/minidump_writer.cc \
|
||||
../../minidump_file_writer.cc \
|
||||
../minidump_writer/linux_dumper.cc \
|
||||
../../../testing/gtest/src/gtest_main.cc \
|
||||
../../../common/string_conversion.cc \
|
||||
../minidump_writer/directory_reader_unittest.cc \
|
||||
../minidump_writer/line_reader_unittest.cc \
|
||||
../minidump_writer/linux_dumper_unittest.cc \
|
||||
../minidump_writer/minidump_writer_unittest.cc
|
||||
|
||||
TEST_C_SRC = ../../../common/convert_UTF.c
|
||||
|
||||
TEST_CC_OBJ=$(patsubst %.cc, $(OBJ_DIR)/%.o,$(TEST_CC_SRC))
|
||||
TEST_C_OBJ=$(patsubst %.c, $(OBJ_DIR)/%.o, $(TEST_C_SRC))
|
||||
|
||||
LINUX_CLIENT_BIN=$(BIN_DIR)/linux_client_test
|
||||
|
||||
BIN=$(LINUX_CLIENT_BIN)
|
||||
|
||||
.PHONY:all clean
|
||||
|
||||
check:$(BIN)
|
||||
$(LINUX_CLIENT_BIN)
|
||||
|
||||
all:$(BIN)
|
||||
|
||||
$(BIN_DIR)/linux_client_test:$(TEST_CC_OBJ) $(TEST_C_OBJ)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(BIN) $(TEST_CC_OBJ) $(TEST_C_OBJ)
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -72,22 +72,43 @@
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/prctl.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/signal.h>
|
||||
#endif
|
||||
#include <sys/syscall.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/ucontext.h>
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
#include <sys/wait.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "common/linux/memory.h"
|
||||
#include "client/linux/minidump_writer//minidump_writer.h"
|
||||
#include "common/memory.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
#include "linux/sched.h"
|
||||
|
||||
#ifndef PR_SET_PTRACER
|
||||
#define PR_SET_PTRACER 0x59616d61
|
||||
#endif
|
||||
|
||||
// A wrapper for the tgkill syscall: send a signal to a specific thread.
|
||||
static int tgkill(pid_t tgid, pid_t tid, int sig) {
|
||||
syscall(__NR_tgkill, tgid, tid, sig);
|
||||
return syscall(__NR_tgkill, tgid, tid, sig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace google_breakpad {
|
||||
@@ -112,23 +133,26 @@ ExceptionHandler::ExceptionHandler(const std::string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context,
|
||||
bool install_handler)
|
||||
: filter_(filter),
|
||||
callback_(callback),
|
||||
callback_context_(callback_context),
|
||||
dump_path_(),
|
||||
handler_installed_(install_handler),
|
||||
crash_handler_(NULL) {
|
||||
set_dump_path(dump_path);
|
||||
: filter_(filter),
|
||||
callback_(callback),
|
||||
callback_context_(callback_context),
|
||||
handler_installed_(install_handler)
|
||||
{
|
||||
Init(dump_path, -1);
|
||||
}
|
||||
|
||||
if (install_handler) {
|
||||
InstallHandlers();
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
if (handler_stack_ == NULL)
|
||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||
handler_stack_->push_back(this);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
ExceptionHandler::ExceptionHandler(const std::string &dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void* callback_context,
|
||||
bool install_handler,
|
||||
const int server_fd)
|
||||
: filter_(filter),
|
||||
callback_(callback),
|
||||
callback_context_(callback_context),
|
||||
handler_installed_(install_handler)
|
||||
{
|
||||
Init(dump_path, server_fd);
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
@@ -136,6 +160,27 @@ ExceptionHandler::~ExceptionHandler() {
|
||||
UninstallHandlers();
|
||||
}
|
||||
|
||||
void ExceptionHandler::Init(const std::string &dump_path,
|
||||
const int server_fd)
|
||||
{
|
||||
crash_handler_ = NULL;
|
||||
if (0 <= server_fd)
|
||||
crash_generation_client_
|
||||
.reset(CrashGenerationClient::TryCreate(server_fd));
|
||||
|
||||
if (handler_installed_)
|
||||
InstallHandlers();
|
||||
|
||||
if (!IsOutOfProcess())
|
||||
set_dump_path(dump_path);
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
if (handler_stack_ == NULL)
|
||||
handler_stack_ = new std::vector<ExceptionHandler *>;
|
||||
handler_stack_->push_back(this);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
bool ExceptionHandler::InstallHandlers() {
|
||||
// We run the signal handlers on an alternative stack because we might have
|
||||
@@ -151,7 +196,7 @@ bool ExceptionHandler::InstallHandlers() {
|
||||
stack.ss_sp = signal_stack;
|
||||
stack.ss_size = kSigStackSize;
|
||||
|
||||
if (sigaltstack(&stack, NULL) == -1)
|
||||
if (sys_sigaltstack(&stack, NULL) == -1)
|
||||
return false;
|
||||
|
||||
struct sigaction sa;
|
||||
@@ -171,6 +216,7 @@ bool ExceptionHandler::InstallHandlers() {
|
||||
return false;
|
||||
old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Runs before crashing: normal context.
|
||||
@@ -181,7 +227,11 @@ void ExceptionHandler::UninstallHandlers() {
|
||||
sigaction(old_handlers_[i].first, action, NULL);
|
||||
delete action;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
std::vector<ExceptionHandler*>::iterator handler =
|
||||
std::find(handler_stack_->begin(), handler_stack_->end(), this);
|
||||
handler_stack_->erase(handler);
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
old_handlers_.clear();
|
||||
}
|
||||
|
||||
@@ -203,12 +253,15 @@ void ExceptionHandler::UpdateNextID() {
|
||||
}
|
||||
}
|
||||
|
||||
// void ExceptionHandler::set_crash_handler(HandlerCallback callback) {
|
||||
// crash_handler_ = callback;
|
||||
// }
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the crashing thread.
|
||||
// static
|
||||
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
|
||||
// All the exception signals are blocked at this point.
|
||||
|
||||
pthread_mutex_lock(&handler_stack_mutex_);
|
||||
|
||||
if (!handler_stack_->size()) {
|
||||
@@ -227,13 +280,28 @@ void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
|
||||
|
||||
pthread_mutex_unlock(&handler_stack_mutex_);
|
||||
|
||||
// Terminate ourselves with the same signal so that our parent knows that we
|
||||
// crashed. The default action for all the signals which we catch is Core, so
|
||||
if (info->si_pid) {
|
||||
// This signal was triggered by somebody sending us the signal with kill().
|
||||
// In order to retrigger it, we have to queue a new signal by calling
|
||||
// kill() ourselves.
|
||||
if (tgkill(getpid(), syscall(__NR_gettid), sig) < 0) {
|
||||
// If we failed to kill ourselves (e.g. because a sandbox disallows us
|
||||
// to do so), we instead resort to terminating our process. This will
|
||||
// result in an incorrect exit code.
|
||||
_exit(1);
|
||||
}
|
||||
} else {
|
||||
// This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV).
|
||||
// No need to reissue the signal. It will automatically trigger again,
|
||||
// when we return from the signal handler.
|
||||
}
|
||||
|
||||
// As soon as we return from the signal handler, our signal will become
|
||||
// unmasked. At that time, we will get terminated with the same signal that
|
||||
// was triggered originally. This allows our parent to know that we crashed.
|
||||
// The default action for all the signals which we catch is Core, so
|
||||
// this is the end of us.
|
||||
signal(sig, SIG_DFL);
|
||||
tgkill(getpid(), sys_gettid(), sig);
|
||||
|
||||
// not reached.
|
||||
}
|
||||
|
||||
struct ThreadArgument {
|
||||
@@ -248,6 +316,11 @@ struct ThreadArgument {
|
||||
// static
|
||||
int ExceptionHandler::ThreadEntry(void *arg) {
|
||||
const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg);
|
||||
|
||||
// Block here until the crashing process unblocks us when
|
||||
// we're allowed to use ptrace
|
||||
thread_arg->handler->WaitForContinueSignal();
|
||||
|
||||
return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context,
|
||||
thread_arg->context_size) == false;
|
||||
}
|
||||
@@ -258,19 +331,39 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
|
||||
// Allow ourselves to be dumped.
|
||||
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||
|
||||
// Allow ourselves to be dumped if the signal is trusted.
|
||||
bool signal_trusted = info->si_code > 0;
|
||||
bool signal_pid_trusted = info->si_code == SI_USER ||
|
||||
info->si_code == SI_TKILL;
|
||||
if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) {
|
||||
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||
}
|
||||
CrashContext context;
|
||||
memcpy(&context.siginfo, info, sizeof(siginfo_t));
|
||||
memcpy(&context.context, uc, sizeof(struct ucontext));
|
||||
memcpy(&context.float_state, ((struct ucontext *)uc)->uc_mcontext.fpregs,
|
||||
sizeof(context.float_state));
|
||||
context.tid = sys_gettid();
|
||||
#if !defined(__ARM_EABI__)
|
||||
// FP state is not part of user ABI on ARM Linux.
|
||||
struct ucontext *uc_ptr = (struct ucontext*)uc;
|
||||
if (uc_ptr->uc_mcontext.fpregs) {
|
||||
memcpy(&context.float_state,
|
||||
uc_ptr->uc_mcontext.fpregs,
|
||||
sizeof(context.float_state));
|
||||
}
|
||||
#endif
|
||||
context.tid = syscall(__NR_gettid);
|
||||
if (crash_handler_ != NULL) {
|
||||
if (crash_handler_(&context, sizeof(context),
|
||||
callback_context_)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return GenerateDump(&context);
|
||||
}
|
||||
|
||||
if (crash_handler_ && crash_handler_(&context, sizeof(context),
|
||||
callback_context_))
|
||||
return true;
|
||||
// This function may run in a compromised context: see the top of the file.
|
||||
bool ExceptionHandler::GenerateDump(CrashContext *context) {
|
||||
if (IsOutOfProcess())
|
||||
return crash_generation_client_->RequestDump(context, sizeof(*context));
|
||||
|
||||
static const unsigned kChildStackSize = 8000;
|
||||
PageAllocator allocator;
|
||||
@@ -284,19 +377,40 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
||||
ThreadArgument thread_arg;
|
||||
thread_arg.handler = this;
|
||||
thread_arg.pid = getpid();
|
||||
thread_arg.context = &context;
|
||||
thread_arg.context_size = sizeof(context);
|
||||
thread_arg.context = context;
|
||||
thread_arg.context_size = sizeof(*context);
|
||||
|
||||
// We need to explicitly enable ptrace of parent processes on some
|
||||
// kernels, but we need to know the PID of the cloned process before we
|
||||
// can do this. Create a pipe here which we can use to block the
|
||||
// cloned process after creating it, until we have explicitly enabled ptrace
|
||||
if(sys_pipe(fdes) == -1) {
|
||||
// Creating the pipe failed. We'll log an error but carry on anyway,
|
||||
// as we'll probably still get a useful crash report. All that will happen
|
||||
// is the write() and read() calls will fail with EBADF
|
||||
static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump \
|
||||
sys_pipe failed:";
|
||||
sys_write(2, no_pipe_msg, sizeof(no_pipe_msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
}
|
||||
|
||||
const pid_t child = sys_clone(
|
||||
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||
&thread_arg, NULL, NULL, NULL);
|
||||
int r, status;
|
||||
// Allow the child to ptrace us
|
||||
prctl(PR_SET_PTRACER, child, 0, 0, 0);
|
||||
SendContinueSignalToChild();
|
||||
do {
|
||||
r = sys_waitpid(child, &status, __WALL);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
sys_close(fdes[0]);
|
||||
sys_close(fdes[1]);
|
||||
|
||||
if (r == -1) {
|
||||
static const char msg[] = "ExceptionHandler::HandleSignal: waitpid failed:";
|
||||
static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:";
|
||||
sys_write(2, msg, sizeof(msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
@@ -311,12 +425,90 @@ bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) {
|
||||
return success;
|
||||
}
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
void ExceptionHandler::SendContinueSignalToChild() {
|
||||
static const char okToContinueMessage = 'a';
|
||||
int r;
|
||||
r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char)));
|
||||
if(r == -1) {
|
||||
static const char msg[] = "ExceptionHandler::SendContinueSignalToChild \
|
||||
sys_write failed:";
|
||||
sys_write(2, msg, sizeof(msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the cloned process.
|
||||
void ExceptionHandler::WaitForContinueSignal() {
|
||||
int r;
|
||||
char receivedMessage;
|
||||
r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char)));
|
||||
if(r == -1) {
|
||||
static const char msg[] = "ExceptionHandler::WaitForContinueSignal \
|
||||
sys_read failed:";
|
||||
sys_write(2, msg, sizeof(msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the cloned process.
|
||||
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
|
||||
size_t context_size) {
|
||||
return google_breakpad::WriteMinidump(
|
||||
next_minidump_path_c_, crashing_process, context, context_size);
|
||||
return google_breakpad::WriteMinidump(next_minidump_path_c_,
|
||||
crashing_process,
|
||||
context,
|
||||
context_size,
|
||||
mapping_list_);
|
||||
}
|
||||
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidump(const std::string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void* callback_context) {
|
||||
ExceptionHandler eh(dump_path, NULL, callback, callback_context, false);
|
||||
return eh.WriteMinidump();
|
||||
}
|
||||
|
||||
bool ExceptionHandler::WriteMinidump() {
|
||||
#if !defined(__ARM_EABI__)
|
||||
// Allow ourselves to be dumped.
|
||||
sys_prctl(PR_SET_DUMPABLE, 1);
|
||||
|
||||
CrashContext context;
|
||||
int getcontext_result = getcontext(&context.context);
|
||||
if (getcontext_result)
|
||||
return false;
|
||||
memcpy(&context.float_state, context.context.uc_mcontext.fpregs,
|
||||
sizeof(context.float_state));
|
||||
context.tid = sys_gettid();
|
||||
|
||||
bool success = GenerateDump(&context);
|
||||
UpdateNextID();
|
||||
return success;
|
||||
#else
|
||||
return false;
|
||||
#endif // !defined(__ARM_EABI__)
|
||||
}
|
||||
|
||||
void ExceptionHandler::AddMappingInfo(const std::string& name,
|
||||
const u_int8_t identifier[sizeof(MDGUID)],
|
||||
uintptr_t start_address,
|
||||
size_t mapping_size,
|
||||
size_t file_offset) {
|
||||
MappingInfo info;
|
||||
info.start_addr = start_address;
|
||||
info.size = mapping_size;
|
||||
info.offset = file_offset;
|
||||
strncpy(info.name, name.c_str(), std::min(name.size(), sizeof(info)));
|
||||
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, identifier, sizeof(MDGUID));
|
||||
mapping_list_.push_back(mapping);
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -30,13 +30,28 @@
|
||||
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "client/linux/android_ucontext.h"
|
||||
#endif
|
||||
#include "client/linux/crash_generation/crash_generation_client.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
struct sigaction;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class ExceptionHandler;
|
||||
|
||||
// ExceptionHandler
|
||||
//
|
||||
// ExceptionHandler can write a minidump file when an exception occurs,
|
||||
@@ -97,7 +112,7 @@ class ExceptionHandler {
|
||||
|
||||
// In certain cases, a user may wish to handle the generation of the minidump
|
||||
// themselves. In this case, they can install a handler callback which is
|
||||
// called when a crash has occured. If this function returns true, no other
|
||||
// called when a crash has occurred. If this function returns true, no other
|
||||
// processing of occurs and the process will shortly be crashed. If this
|
||||
// returns false, the normal processing continues.
|
||||
typedef bool (*HandlerCallback)(const void* crash_context,
|
||||
@@ -116,6 +131,18 @@ class ExceptionHandler {
|
||||
FilterCallback filter, MinidumpCallback callback,
|
||||
void *callback_context,
|
||||
bool install_handler);
|
||||
|
||||
// Creates a new ExceptionHandler instance that can attempt to
|
||||
// perform out-of-process dump generation if server_fd is valid. If
|
||||
// server_fd is invalid, in-process dump generation will be
|
||||
// used. See the above ctor for a description of the other
|
||||
// parameters.
|
||||
ExceptionHandler(const std::string& dump_path,
|
||||
FilterCallback filter, MinidumpCallback callback,
|
||||
void* callback_context,
|
||||
bool install_handler,
|
||||
const int server_fd);
|
||||
|
||||
~ExceptionHandler();
|
||||
|
||||
// Get and set the minidump path.
|
||||
@@ -146,13 +173,35 @@ class ExceptionHandler {
|
||||
siginfo_t siginfo;
|
||||
pid_t tid; // the crashing thread.
|
||||
struct ucontext context;
|
||||
#if !defined(__ARM_EABI__)
|
||||
// #ifdef this out because FP state is not part of user ABI for Linux ARM.
|
||||
struct _libc_fpstate float_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
// Returns whether out-of-process dump generation is used or not.
|
||||
bool IsOutOfProcess() const {
|
||||
return crash_generation_client_.get() != NULL;
|
||||
}
|
||||
|
||||
// Add information about a memory mapping. This can be used if
|
||||
// a custom library loader is used that maps things in a way
|
||||
// that the linux dumper can't handle by reading the maps file.
|
||||
void AddMappingInfo(const std::string& name,
|
||||
const u_int8_t identifier[sizeof(MDGUID)],
|
||||
uintptr_t start_address,
|
||||
size_t mapping_size,
|
||||
size_t file_offset);
|
||||
|
||||
private:
|
||||
void Init(const std::string &dump_path,
|
||||
const int server_fd);
|
||||
bool InstallHandlers();
|
||||
void UninstallHandlers();
|
||||
void PreresolveSymbols();
|
||||
bool GenerateDump(CrashContext *context);
|
||||
void SendContinueSignalToChild();
|
||||
void WaitForContinueSignal();
|
||||
|
||||
void UpdateNextID();
|
||||
static void SignalHandler(int sig, siginfo_t* info, void* uc);
|
||||
@@ -165,6 +214,8 @@ class ExceptionHandler {
|
||||
const MinidumpCallback callback_;
|
||||
void* const callback_context_;
|
||||
|
||||
scoped_ptr<CrashGenerationClient> crash_generation_client_;
|
||||
|
||||
std::string dump_path_;
|
||||
std::string next_minidump_path_;
|
||||
std::string next_minidump_id_;
|
||||
@@ -188,9 +239,19 @@ class ExceptionHandler {
|
||||
static unsigned handler_stack_index_;
|
||||
static pthread_mutex_t handler_stack_mutex_;
|
||||
|
||||
// A vector of the old signal handlers. The void* is a pointer to a newly
|
||||
// allocated sigaction structure to avoid pulling in too many includes.
|
||||
std::vector<std::pair<int, void *> > old_handlers_;
|
||||
// A vector of the old signal handlers.
|
||||
std::vector<std::pair<int, struct sigaction *> > old_handlers_;
|
||||
|
||||
// We need to explicitly enable ptrace of parent processes on some
|
||||
// kernels, but we need to know the PID of the cloned process before we
|
||||
// can do this. We create a pipe which we can use to block the
|
||||
// cloned process after creating it, until we have explicitly enabled
|
||||
// ptrace. This is used to store the file descriptors for the pipe
|
||||
int fdes[2];
|
||||
|
||||
// Callers can add extra info about mappings for cases where the
|
||||
// dumper code cannot extract enough information from /proc/<pid>/maps.
|
||||
MappingList mapping_list_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2010 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -27,33 +27,38 @@
|
||||
// (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 <string>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "client/linux/handler//exception_handler.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
// This provides a wrapper around system calls which may be
|
||||
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
typeof(x) __eintr_result__; \
|
||||
do { \
|
||||
__eintr_result__ = x; \
|
||||
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||
__eintr_result__;\
|
||||
})
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
// Length of a formatted GUID string =
|
||||
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||
const int kGUIDStringSize = 37;
|
||||
|
||||
static void sigchld_handler(int signo) { }
|
||||
|
||||
class ExceptionHandlerTest : public ::testing::Test {
|
||||
@@ -74,7 +79,7 @@ class ExceptionHandlerTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST(ExceptionHandlerTest, Simple) {
|
||||
ExceptionHandler handler("/tmp", NULL, NULL, NULL, true);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, NULL, NULL, true);
|
||||
}
|
||||
|
||||
static bool DoneCallback(const char* dump_path,
|
||||
@@ -84,7 +89,7 @@ static bool DoneCallback(const char* dump_path,
|
||||
if (!succeeded)
|
||||
return succeeded;
|
||||
|
||||
int fd = (int) context;
|
||||
int fd = (intptr_t) context;
|
||||
uint32_t len = my_strlen(minidump_id);
|
||||
HANDLE_EINTR(sys_write(fd, &len, sizeof(len)));
|
||||
HANDLE_EINTR(sys_write(fd, minidump_id, len));
|
||||
@@ -100,9 +105,9 @@ TEST(ExceptionHandlerTest, ChildCrash) {
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler("/tmp", NULL, DoneCallback, (void*) fds[1],
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
*reinterpret_cast<int*>(NULL) = 0;
|
||||
*reinterpret_cast<volatile int*>(NULL) = 0;
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
@@ -121,14 +126,14 @@ TEST(ExceptionHandlerTest, ChildCrash) {
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
|
||||
uint32_t len;
|
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), sizeof(len));
|
||||
ASSERT_LT(len, 2048);
|
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
|
||||
ASSERT_LT(len, (uint32_t)2048);
|
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1));
|
||||
ASSERT_EQ(read(fds[0], filename, len), len);
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string("/tmp/") + filename +
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
@@ -137,21 +142,536 @@ TEST(ExceptionHandlerTest, ChildCrash) {
|
||||
unlink(minidump_filename.c_str());
|
||||
}
|
||||
|
||||
// Test that memory around the instruction pointer is written
|
||||
// to the dump as a MinidumpMemoryRegion.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemory) {
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = 256; // bytes
|
||||
const int kOffset = kMemorySize / 2;
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
if (!memory)
|
||||
exit(0);
|
||||
|
||||
// Write some instructions that will crash. Put them in the middle
|
||||
// of the block of memory, because the minidump should contain 128
|
||||
// bytes on either side of the instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
memory_function();
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
int status;
|
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(WTERMSIG(status), SIGILL);
|
||||
|
||||
struct pollfd pfd;
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = fds[0];
|
||||
pfd.events = POLLIN | POLLERR;
|
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
|
||||
ASSERT_EQ(r, 1);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
|
||||
uint32_t len;
|
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
|
||||
ASSERT_LT(len, (uint32_t)2048);
|
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1));
|
||||
ASSERT_EQ(read(fds[0], filename, len), len);
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_LT(0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
u_int64_t instruction_pointer;
|
||||
switch (context->GetContextCPU()) {
|
||||
case MD_CONTEXT_X86:
|
||||
instruction_pointer = context->GetContextX86()->eip;
|
||||
break;
|
||||
case MD_CONTEXT_AMD64:
|
||||
instruction_pointer = context->GetContextAMD64()->rip;
|
||||
break;
|
||||
case MD_CONTEXT_ARM:
|
||||
instruction_pointer = context->GetContextARM()->iregs[15];
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
|
||||
break;
|
||||
}
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
EXPECT_EQ(kMemorySize, region->GetSize());
|
||||
const u_int8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
u_int8_t prefix_bytes[kOffset];
|
||||
u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
|
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes));
|
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes));
|
||||
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
|
||||
suffix_bytes, sizeof(suffix_bytes)) == 0);
|
||||
|
||||
unlink(minidump_filename.c_str());
|
||||
free(filename);
|
||||
}
|
||||
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the low end.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = 256; // bytes
|
||||
const int kOffset = 0;
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
if (!memory)
|
||||
exit(0);
|
||||
|
||||
// Write some instructions that will crash. Put them in the middle
|
||||
// of the block of memory, because the minidump should contain 128
|
||||
// bytes on either side of the instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
memory_function();
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
int status;
|
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(WTERMSIG(status), SIGILL);
|
||||
|
||||
struct pollfd pfd;
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = fds[0];
|
||||
pfd.events = POLLIN | POLLERR;
|
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
|
||||
ASSERT_EQ(r, 1);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
|
||||
uint32_t len;
|
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
|
||||
ASSERT_LT(len, (uint32_t)2048);
|
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1));
|
||||
ASSERT_EQ(read(fds[0], filename, len), len);
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_LT(0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
u_int64_t instruction_pointer;
|
||||
switch (context->GetContextCPU()) {
|
||||
case MD_CONTEXT_X86:
|
||||
instruction_pointer = context->GetContextX86()->eip;
|
||||
break;
|
||||
case MD_CONTEXT_AMD64:
|
||||
instruction_pointer = context->GetContextAMD64()->rip;
|
||||
break;
|
||||
case MD_CONTEXT_ARM:
|
||||
instruction_pointer = context->GetContextARM()->iregs[15];
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
|
||||
break;
|
||||
}
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
EXPECT_EQ(kMemorySize / 2, region->GetSize());
|
||||
const u_int8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
|
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes));
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
|
||||
suffix_bytes, sizeof(suffix_bytes)) == 0);
|
||||
|
||||
unlink(minidump_filename.c_str());
|
||||
free(filename);
|
||||
}
|
||||
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the high end.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
// Use 4k here because the OS will hand out a single page even
|
||||
// if a smaller size is requested, and this test wants to
|
||||
// test the upper bound of the memory range.
|
||||
const u_int32_t kMemorySize = 4096; // bytes
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
const int kOffset = kMemorySize - sizeof(instructions);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
if (!memory)
|
||||
exit(0);
|
||||
|
||||
// Write some instructions that will crash. Put them in the middle
|
||||
// of the block of memory, because the minidump should contain 128
|
||||
// bytes on either side of the instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
memory_function();
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
int status;
|
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(WTERMSIG(status), SIGILL);
|
||||
|
||||
struct pollfd pfd;
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = fds[0];
|
||||
pfd.events = POLLIN | POLLERR;
|
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
|
||||
ASSERT_EQ(r, 1);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
|
||||
uint32_t len;
|
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
|
||||
ASSERT_LT(len, (uint32_t)2048);
|
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1));
|
||||
ASSERT_EQ(read(fds[0], filename, len), len);
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_LT(0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
u_int64_t instruction_pointer;
|
||||
switch (context->GetContextCPU()) {
|
||||
case MD_CONTEXT_X86:
|
||||
instruction_pointer = context->GetContextX86()->eip;
|
||||
break;
|
||||
case MD_CONTEXT_AMD64:
|
||||
instruction_pointer = context->GetContextAMD64()->rip;
|
||||
break;
|
||||
case MD_CONTEXT_ARM:
|
||||
instruction_pointer = context->GetContextARM()->iregs[15];
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
|
||||
break;
|
||||
}
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
const size_t kPrefixSize = 128; // bytes
|
||||
EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
|
||||
const u_int8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
u_int8_t prefix_bytes[kPrefixSize];
|
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes));
|
||||
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kPrefixSize,
|
||||
instructions, sizeof(instructions)) == 0);
|
||||
|
||||
unlink(minidump_filename.c_str());
|
||||
free(filename);
|
||||
}
|
||||
|
||||
// Ensure that an extra memory block doesn't get added when the
|
||||
// instruction pointer is not in mapped memory.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
// Try calling a NULL pointer.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(NULL);
|
||||
memory_function();
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
int status;
|
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(WTERMSIG(status), SIGSEGV);
|
||||
|
||||
struct pollfd pfd;
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = fds[0];
|
||||
pfd.events = POLLIN | POLLERR;
|
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 0));
|
||||
ASSERT_EQ(r, 1);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
|
||||
uint32_t len;
|
||||
ASSERT_EQ(read(fds[0], &len, sizeof(len)), (ssize_t)sizeof(len));
|
||||
ASSERT_LT(len, (uint32_t)2048);
|
||||
char* filename = reinterpret_cast<char*>(malloc(len + 1));
|
||||
ASSERT_EQ(read(fds[0], filename, len), len);
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(minidump_filename.c_str(), &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_EQ((unsigned int)1, memory_list->region_count());
|
||||
|
||||
unlink(minidump_filename.c_str());
|
||||
free(filename);
|
||||
}
|
||||
|
||||
static bool SimpleCallback(const char* dump_path,
|
||||
const char* minidump_id,
|
||||
void* context,
|
||||
bool succeeded) {
|
||||
if (!succeeded)
|
||||
return succeeded;
|
||||
|
||||
string* minidump_file = reinterpret_cast<string*>(context);
|
||||
minidump_file->append(dump_path);
|
||||
minidump_file->append("/");
|
||||
minidump_file->append(minidump_id);
|
||||
minidump_file->append(".dmp");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Test that anonymous memory maps can be annotated with names and IDs.
|
||||
TEST(ExceptionHandlerTest, ModuleInfo) {
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||
const char* kMemoryName = "a fake module";
|
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
char module_identifier_buffer[kGUIDStringSize];
|
||||
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||
module_identifier_buffer,
|
||||
sizeof(module_identifier_buffer));
|
||||
string module_identifier(module_identifier_buffer);
|
||||
// Strip out dashes
|
||||
size_t pos;
|
||||
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||
module_identifier.erase(pos, 1);
|
||||
}
|
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
|
||||
// Get some memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
string minidump_filename;
|
||||
ExceptionHandler handler(TEMPDIR, NULL, SimpleCallback,
|
||||
(void*)&minidump_filename, true);
|
||||
// Add info about the anonymous memory mapping.
|
||||
handler.AddMappingInfo(kMemoryName,
|
||||
kModuleGUID,
|
||||
kMemoryAddress,
|
||||
kMemorySize,
|
||||
0);
|
||||
handler.WriteMinidump();
|
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(minidump_filename);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module =
|
||||
module_list->GetModuleForAddress(kMemoryAddress);
|
||||
ASSERT_TRUE(module);
|
||||
|
||||
EXPECT_EQ(kMemoryAddress, module->base_address());
|
||||
EXPECT_EQ(kMemorySize, module->size());
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(minidump_filename.c_str());
|
||||
}
|
||||
|
||||
static const unsigned kControlMsgSize =
|
||||
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
|
||||
|
||||
static bool
|
||||
CrashHandler(const void* crash_context, size_t crash_context_size,
|
||||
void* context) {
|
||||
const int fd = (int) context;
|
||||
const int fd = (intptr_t) context;
|
||||
int fds[2];
|
||||
pipe(fds);
|
||||
|
||||
struct kernel_msghdr msg = {0};
|
||||
struct kernel_iovec iov;
|
||||
iov.iov_base = const_cast<void*>(crash_context);
|
||||
iov.iov_len = crash_context_size;
|
||||
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
char cmsg[kControlMsgSize];
|
||||
@@ -192,11 +712,10 @@ TEST(ExceptionHandlerTest, ExternalDumper) {
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler("/tmp", NULL, NULL, (void*) fds[1], true);
|
||||
ExceptionHandler handler("/tmp1", NULL, NULL, (void*) fds[1], true);
|
||||
handler.set_crash_handler(CrashHandler);
|
||||
*reinterpret_cast<int*>(NULL) = 0;
|
||||
*reinterpret_cast<volatile int*>(NULL) = 0;
|
||||
}
|
||||
|
||||
close(fds[1]);
|
||||
struct msghdr msg = {0};
|
||||
struct iovec iov;
|
||||
@@ -237,7 +756,7 @@ TEST(ExceptionHandlerTest, ExternalDumper) {
|
||||
ASSERT_NE(crashing_pid, -1);
|
||||
ASSERT_NE(signal_fd, -1);
|
||||
|
||||
char templ[] = "/tmp/exception-handler-unittest-XXXXXX";
|
||||
char templ[] = TEMPDIR "/exception-handler-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
ASSERT_TRUE(WriteMinidump(templ, crashing_pid, context,
|
||||
kCrashContextSize));
|
||||
|
@@ -37,7 +37,7 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
|
@@ -34,7 +34,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
|
@@ -36,8 +36,14 @@
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
static int TemporaryFile() {
|
||||
static const char templ[] = "/tmp/line-reader-unittest-XXXXXX";
|
||||
static const char templ[] = TEMPDIR "/line-reader-unittest-XXXXXX";
|
||||
char templ_copy[sizeof(templ)];
|
||||
memcpy(templ_copy, templ, sizeof(templ));
|
||||
const int fd = mkstemp(templ_copy);
|
||||
@@ -69,9 +75,9 @@ TEST(LineReaderTest, OneLineTerminated) {
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
unsigned int len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(len, (unsigned int)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
@@ -90,7 +96,7 @@ TEST(LineReaderTest, OneLine) {
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
@@ -109,13 +115,13 @@ TEST(LineReaderTest, TwoLinesTerminated) {
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
@@ -134,13 +140,13 @@ TEST(LineReaderTest, TwoLines) {
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, 1);
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2010, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -34,25 +34,32 @@
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <link.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
static const char kMappedFileUnsafePrefix[] = "/dev/";
|
||||
static const char kDeletedSuffix[] = " (deleted)";
|
||||
|
||||
// Suspend a thread by attaching to it.
|
||||
static bool SuspendThread(pid_t pid) {
|
||||
@@ -68,6 +75,26 @@ static bool SuspendThread(pid_t pid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
// On x86, the stack pointer is NULL or -1, when executing trusted code in
|
||||
// the seccomp sandbox. Not only does this cause difficulties down the line
|
||||
// when trying to dump the thread's stack, it also results in the minidumps
|
||||
// containing information about the trusted threads. This information is
|
||||
// generally completely meaningless and just pollutes the minidumps.
|
||||
// We thus test the stack pointer and exclude any threads that are part of
|
||||
// the seccomp sandbox's trusted code.
|
||||
user_regs_struct regs;
|
||||
if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 ||
|
||||
#if defined(__i386)
|
||||
!regs.esp
|
||||
#elif defined(__x86_64)
|
||||
!regs.rsp
|
||||
#endif
|
||||
) {
|
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -76,11 +103,22 @@ static bool ResumeThread(pid_t pid) {
|
||||
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
|
||||
}
|
||||
|
||||
inline static bool IsMappedFileOpenUnsafe(
|
||||
const google_breakpad::MappingInfo& mapping) {
|
||||
// It is unsafe to attempt to open a mapped file that lives under /dev,
|
||||
// because the semantics of the open may be driver-specific so we'd risk
|
||||
// hanging the crash dumper. And a file in /dev/ almost certainly has no
|
||||
// ELF file identifier anyways.
|
||||
return my_strncmp(mapping.name,
|
||||
kMappedFileUnsafePrefix,
|
||||
sizeof(kMappedFileUnsafePrefix) - 1) == 0;
|
||||
}
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxDumper::LinuxDumper(int pid)
|
||||
: pid_(pid),
|
||||
threads_suspened_(false),
|
||||
threads_suspended_(false),
|
||||
threads_(&allocator_, 8),
|
||||
mappings_(&allocator_) {
|
||||
}
|
||||
@@ -91,22 +129,30 @@ bool LinuxDumper::Init() {
|
||||
}
|
||||
|
||||
bool LinuxDumper::ThreadsSuspend() {
|
||||
if (threads_suspened_)
|
||||
if (threads_suspended_)
|
||||
return true;
|
||||
bool good = true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i)
|
||||
good &= SuspendThread(threads_[i]);
|
||||
threads_suspened_ = true;
|
||||
return good;
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
if (!SuspendThread(threads_[i])) {
|
||||
// If the thread either disappeared before we could attach to it, or if
|
||||
// it was part of the seccomp sandbox's trusted code, it is OK to
|
||||
// silently drop it from the minidump.
|
||||
memmove(&threads_[i], &threads_[i+1],
|
||||
(threads_.size() - i - 1) * sizeof(threads_[i]));
|
||||
threads_.resize(threads_.size() - 1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
threads_suspended_ = true;
|
||||
return threads_.size() > 0;
|
||||
}
|
||||
|
||||
bool LinuxDumper::ThreadsResume() {
|
||||
if (!threads_suspened_)
|
||||
if (!threads_suspended_)
|
||||
return false;
|
||||
bool good = true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i)
|
||||
good &= ResumeThread(threads_[i]);
|
||||
threads_suspened_ = false;
|
||||
threads_suspended_ = false;
|
||||
return good;
|
||||
}
|
||||
|
||||
@@ -153,12 +199,73 @@ LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
|
||||
my_itos(path + 6, pid, pid_len);
|
||||
memcpy(path + 6 + pid_len, "/", 1);
|
||||
memcpy(path + 6 + pid_len + 1, node, node_len);
|
||||
memcpy(path + total_length, "\0", 1);
|
||||
path[total_length] = '\0';
|
||||
}
|
||||
|
||||
bool
|
||||
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
bool member,
|
||||
unsigned int mapping_id,
|
||||
uint8_t identifier[sizeof(MDGUID)])
|
||||
{
|
||||
assert(!member || mapping_id < mappings_.size());
|
||||
my_memset(identifier, 0, sizeof(MDGUID));
|
||||
if (IsMappedFileOpenUnsafe(mapping))
|
||||
return false;
|
||||
|
||||
// Special-case linux-gate because it's not a real file.
|
||||
if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) {
|
||||
const uintptr_t kPageSize = getpagesize();
|
||||
void* linux_gate = NULL;
|
||||
if (pid_ == sys_getpid()) {
|
||||
linux_gate = reinterpret_cast<void*>(mapping.start_addr);
|
||||
} else {
|
||||
linux_gate = allocator_.Alloc(kPageSize);
|
||||
CopyFromProcess(linux_gate, pid_,
|
||||
reinterpret_cast<const void*>(mapping.start_addr),
|
||||
kPageSize);
|
||||
}
|
||||
return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
|
||||
}
|
||||
|
||||
char filename[NAME_MAX];
|
||||
size_t filename_len = my_strlen(mapping.name);
|
||||
assert(filename_len < NAME_MAX);
|
||||
if (filename_len >= NAME_MAX)
|
||||
return false;
|
||||
memcpy(filename, mapping.name, filename_len);
|
||||
filename[filename_len] = '\0';
|
||||
bool filename_modified = HandleDeletedFileInMapping(filename);
|
||||
|
||||
int fd = sys_open(filename, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
struct kernel_stat st;
|
||||
if (sys_fstat(fd, &st) != 0) {
|
||||
sys_close(fd);
|
||||
return false;
|
||||
}
|
||||
#if defined(__x86_64)
|
||||
#define sys_mmap2 sys_mmap
|
||||
#endif
|
||||
void* base = sys_mmap2(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
sys_close(fd);
|
||||
if (base == MAP_FAILED)
|
||||
return false;
|
||||
|
||||
bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
|
||||
sys_munmap(base, st.st_size);
|
||||
if (success && member && filename_modified) {
|
||||
mappings_[mapping_id]->name[filename_len -
|
||||
sizeof(kDeletedSuffix) + 1] = '\0';
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void*
|
||||
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
|
||||
char auxv_path[80];
|
||||
char auxv_path[NAME_MAX];
|
||||
BuildProcPath(auxv_path, pid, "auxv");
|
||||
|
||||
// If BuildProcPath errors out due to invalid input, we'll handle it when
|
||||
@@ -188,7 +295,7 @@ LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
|
||||
|
||||
bool
|
||||
LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
char maps_path[80];
|
||||
char maps_path[NAME_MAX];
|
||||
BuildProcPath(maps_path, pid_, "maps");
|
||||
|
||||
// linux_gate_loc is the beginning of the kernel's mapping of
|
||||
@@ -215,25 +322,36 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
if (*i2 == ' ') {
|
||||
const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
|
||||
if (*i3 == ' ') {
|
||||
const char* name = NULL;
|
||||
// Only copy name if the name is a valid path name, or if
|
||||
// it's the VDSO image.
|
||||
if (((name = my_strchr(line, '/')) == NULL) &&
|
||||
linux_gate_loc &&
|
||||
reinterpret_cast<void*>(start_addr) == linux_gate_loc) {
|
||||
name = kLinuxGateLibraryName;
|
||||
offset = 0;
|
||||
}
|
||||
// Merge adjacent mappings with the same name into one module,
|
||||
// assuming they're a single library mapped by the dynamic linker
|
||||
if (name && result->size()) {
|
||||
MappingInfo* module = (*result)[result->size() - 1];
|
||||
if ((start_addr == module->start_addr + module->size) &&
|
||||
(my_strlen(name) == my_strlen(module->name)) &&
|
||||
(my_strncmp(name, module->name, my_strlen(name)) == 0)) {
|
||||
module->size = end_addr - module->start_addr;
|
||||
line_reader->PopLine(line_len);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MappingInfo* const module = new(allocator_) MappingInfo;
|
||||
memset(module, 0, sizeof(MappingInfo));
|
||||
module->start_addr = start_addr;
|
||||
module->size = end_addr - start_addr;
|
||||
module->offset = offset;
|
||||
const char* name = NULL;
|
||||
// Only copy name if the name is a valid path name, or if
|
||||
// we've found the VDSO image
|
||||
if ((name = my_strchr(line, '/')) != NULL) {
|
||||
if (name != NULL) {
|
||||
const unsigned l = my_strlen(name);
|
||||
if (l < sizeof(module->name))
|
||||
memcpy(module->name, name, l);
|
||||
} else if (linux_gate_loc &&
|
||||
reinterpret_cast<void*>(module->start_addr) ==
|
||||
linux_gate_loc) {
|
||||
memcpy(module->name,
|
||||
kLinuxGateLibraryName,
|
||||
my_strlen(kLinuxGateLibraryName));
|
||||
module->offset = 0;
|
||||
}
|
||||
result->push_back(module);
|
||||
}
|
||||
@@ -250,7 +368,7 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
// Parse /proc/$pid/task to list all the threads of the process identified by
|
||||
// pid.
|
||||
bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
|
||||
char task_path[80];
|
||||
char task_path[NAME_MAX];
|
||||
BuildProcPath(task_path, pid_, "task");
|
||||
|
||||
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
|
||||
@@ -280,12 +398,12 @@ bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
|
||||
}
|
||||
|
||||
// Read thread info from /proc/$pid/status.
|
||||
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible,
|
||||
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
|
||||
// these members are set to -1. Returns true iff all three members are
|
||||
// availible.
|
||||
// available.
|
||||
bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
|
||||
assert(info != NULL);
|
||||
char status_path[80];
|
||||
char status_path[NAME_MAX];
|
||||
BuildProcPath(status_path, tid, "status");
|
||||
|
||||
const int fd = open(status_path, O_RDONLY);
|
||||
@@ -311,15 +429,22 @@ bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
|
||||
if (info->ppid == -1 || info->tgid == -1)
|
||||
return false;
|
||||
|
||||
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 ||
|
||||
sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
|
||||
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
#if !defined(__ANDROID__)
|
||||
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__i386)
|
||||
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
|
||||
if (sys_ptrace(
|
||||
PTRACE_PEEKUSER, tid,
|
||||
@@ -337,15 +462,14 @@ bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
|
||||
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
|
||||
#elif defined(__x86_64)
|
||||
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
|
||||
#elif defined(__ARM_EABI__)
|
||||
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
|
||||
#else
|
||||
#error "This code hasn't been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
if (!GetStackInfo(&info->stack, &info->stack_len,
|
||||
(uintptr_t) stack_pointer))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return GetStackInfo(&info->stack, &info->stack_len,
|
||||
(uintptr_t) stack_pointer);
|
||||
}
|
||||
|
||||
// Get information about the stack, given the stack pointer. We don't try to
|
||||
@@ -353,42 +477,31 @@ bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
|
||||
// unwind. So we just grab, up to, 32k of stack.
|
||||
bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
|
||||
uintptr_t int_stack_pointer) {
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
static const bool stack_grows_down = true;
|
||||
static const uintptr_t page_size = 4096;
|
||||
#else
|
||||
#error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
// Move the stack pointer to the bottom of the page that it's in.
|
||||
const uintptr_t page_size = getpagesize();
|
||||
|
||||
uint8_t* const stack_pointer =
|
||||
reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
|
||||
|
||||
// The number of bytes of stack which we try to capture.
|
||||
static unsigned kStackToCapture = 32 * 1024;
|
||||
static const ptrdiff_t kStackToCapture = 32 * 1024;
|
||||
|
||||
const MappingInfo* mapping = FindMapping(stack_pointer);
|
||||
if (!mapping)
|
||||
return false;
|
||||
if (stack_grows_down) {
|
||||
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||
const ptrdiff_t distance_to_end =
|
||||
static_cast<ptrdiff_t>(mapping->size) - offset;
|
||||
*stack_len = distance_to_end > kStackToCapture ?
|
||||
kStackToCapture : distance_to_end;
|
||||
*stack = stack_pointer;
|
||||
} else {
|
||||
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||
*stack_len = offset > kStackToCapture ? kStackToCapture : offset;
|
||||
*stack = stack_pointer - *stack_len;
|
||||
}
|
||||
|
||||
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||
const ptrdiff_t distance_to_end =
|
||||
static_cast<ptrdiff_t>(mapping->size) - offset;
|
||||
*stack_len = distance_to_end > kStackToCapture ?
|
||||
kStackToCapture : distance_to_end;
|
||||
*stack = stack_pointer;
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length) {
|
||||
unsigned long tmp;
|
||||
unsigned long tmp = 55;
|
||||
size_t done = 0;
|
||||
static const size_t word_size = sizeof(tmp);
|
||||
uint8_t* const local = (uint8_t*) dest;
|
||||
@@ -396,8 +509,9 @@ void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
|
||||
while (done < length) {
|
||||
const size_t l = length - done > word_size ? word_size : length - done;
|
||||
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1)
|
||||
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) {
|
||||
tmp = 0;
|
||||
}
|
||||
memcpy(local + done, &tmp, l);
|
||||
done += l;
|
||||
}
|
||||
@@ -416,4 +530,42 @@ const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
|
||||
static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
|
||||
|
||||
// Check for ' (deleted)' in |path|.
|
||||
// |path| has to be at least as long as "/x (deleted)".
|
||||
const size_t path_len = my_strlen(path);
|
||||
if (path_len < kDeletedSuffixLen + 2)
|
||||
return false;
|
||||
if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix,
|
||||
kDeletedSuffixLen) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check |path| against the /proc/pid/exe 'symlink'.
|
||||
char exe_link[NAME_MAX];
|
||||
char new_path[NAME_MAX];
|
||||
BuildProcPath(exe_link, pid_, "exe");
|
||||
ssize_t new_path_len = sys_readlink(exe_link, new_path, NAME_MAX);
|
||||
if (new_path_len <= 0 || new_path_len == NAME_MAX)
|
||||
return false;
|
||||
new_path[new_path_len] = '\0';
|
||||
if (my_strcmp(path, new_path) != 0)
|
||||
return false;
|
||||
|
||||
// Check to see if someone actually named their executable 'foo (deleted)'.
|
||||
struct kernel_stat exe_stat;
|
||||
struct kernel_stat new_path_stat;
|
||||
if (sys_stat(exe_link, &exe_stat) == 0 &&
|
||||
sys_stat(new_path, &new_path_stat) == 0 &&
|
||||
exe_stat.st_dev == new_path_stat.st_dev &&
|
||||
exe_stat.st_ino == new_path_stat.st_ino) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(path, exe_link, NAME_MAX);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2010, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -31,19 +31,41 @@
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||
|
||||
#include <elf.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/user.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
|
||||
#include "common/linux/memory.h"
|
||||
#include "common/memory.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
|
||||
#endif
|
||||
|
||||
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
|
||||
#if defined(__i386)
|
||||
#if defined(__i386) || defined(__ARM_EABI__)
|
||||
#if !defined(__ANDROID__)
|
||||
typedef Elf32_auxv_t elf_aux_entry;
|
||||
#else
|
||||
// Android is missing this structure definition
|
||||
typedef struct
|
||||
{
|
||||
uint32_t a_type; /* Entry type */
|
||||
union
|
||||
{
|
||||
uint32_t a_val; /* Integer value */
|
||||
} a_un;
|
||||
} elf_aux_entry;
|
||||
|
||||
#if !defined(AT_SYSINFO_EHDR)
|
||||
#define AT_SYSINFO_EHDR 33
|
||||
#endif
|
||||
#endif // __ANDROID__
|
||||
#elif defined(__x86_64__)
|
||||
typedef Elf64_auxv_t elf_aux_entry;
|
||||
#endif
|
||||
@@ -62,13 +84,24 @@ struct ThreadInfo {
|
||||
const void* stack; // pointer to the stack area
|
||||
size_t stack_len; // length of the stack to copy
|
||||
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
user_regs_struct regs;
|
||||
user_fpregs_struct fpregs;
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
user_fpxregs_struct fpxregs;
|
||||
|
||||
static const unsigned kNumDebugRegisters = 8;
|
||||
debugreg_t dregs[8];
|
||||
#if defined(__i386)
|
||||
user_fpxregs_struct fpxregs;
|
||||
#endif // defined(__i386)
|
||||
|
||||
#elif defined(__ARM_EABI__)
|
||||
// Mimicking how strace does this(see syscall.c, search for GETREGS)
|
||||
#if defined(__ANDROID__)
|
||||
struct pt_regs regs;
|
||||
#else
|
||||
struct user_regs regs;
|
||||
struct user_fpregs fpregs;
|
||||
#endif // __ANDROID__
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -118,6 +151,13 @@ class LinuxDumper {
|
||||
// without any slashes.
|
||||
void BuildProcPath(char* path, pid_t pid, const char* node) const;
|
||||
|
||||
// Generate a File ID from the .text section of a mapped entry.
|
||||
// If not a member, mapping_id is ignored.
|
||||
bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
bool member,
|
||||
unsigned int mapping_id,
|
||||
uint8_t identifier[sizeof(MDGUID)]);
|
||||
|
||||
// Utility method to find the location of where the kernel has
|
||||
// mapped linux-gate.so in memory(shows up in /proc/pid/maps as
|
||||
// [vdso], but we can't guarantee that it's the only virtual dynamic
|
||||
@@ -128,11 +168,22 @@ class LinuxDumper {
|
||||
bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
|
||||
bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
|
||||
|
||||
// For the case where a running program has been deleted, it'll show up in
|
||||
// /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then
|
||||
// see if '/path/to/program (deleted)' matches /proc/pid/exe and return
|
||||
// /proc/pid/exe in |path| so ELF identifier generation works correctly. This
|
||||
// also checks to see if '/path/to/program (deleted)' exists, so it does not
|
||||
// get fooled by a poorly named binary.
|
||||
// For programs that don't end with ' (deleted)', this is a no-op.
|
||||
// This assumes |path| is a buffer with length NAME_MAX.
|
||||
// Returns true if |path| is modified.
|
||||
bool HandleDeletedFileInMapping(char* path) const;
|
||||
|
||||
const pid_t pid_;
|
||||
|
||||
mutable PageAllocator allocator_;
|
||||
|
||||
bool threads_suspened_;
|
||||
bool threads_suspended_;
|
||||
wasteful_vector<pid_t> threads_; // the ids of all the threads
|
||||
wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
|
||||
};
|
||||
|
@@ -27,15 +27,47 @@
|
||||
// (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 <string>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/memory.h"
|
||||
|
||||
using std::string;
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test LinuxDumperTest;
|
||||
|
||||
string GetHelperBinary() {
|
||||
// Locate helper binary next to the current binary.
|
||||
char self_path[PATH_MAX];
|
||||
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
|
||||
return "";
|
||||
}
|
||||
string helper_path(self_path);
|
||||
size_t pos = helper_path.rfind('/');
|
||||
if (pos == string::npos) {
|
||||
return "";
|
||||
}
|
||||
helper_path.erase(pos + 1);
|
||||
helper_path += "linux_dumper_unittest_helper";
|
||||
|
||||
return helper_path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, Setup) {
|
||||
@@ -55,7 +87,7 @@ TEST(LinuxDumperTest, ThreadList) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
ASSERT_GE(dumper.threads().size(), 1);
|
||||
ASSERT_GE(dumper.threads().size(), (size_t)1);
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||
if (dumper.threads()[i] == getpid()) {
|
||||
@@ -65,6 +97,155 @@ TEST(LinuxDumperTest, ThreadList) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper stack class to close a file descriptor and unmap
|
||||
// a mmap'ed mapping.
|
||||
class StackHelper {
|
||||
public:
|
||||
StackHelper(int fd, char* mapping, size_t size)
|
||||
: fd_(fd), mapping_(mapping), size_(size) {}
|
||||
~StackHelper() {
|
||||
munmap(mapping_, size_);
|
||||
close(fd_);
|
||||
}
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
char* mapping_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
TEST(LinuxDumperTest, MergedMappings) {
|
||||
string helper_path(GetHelperBinary());
|
||||
if (helper_path.empty()) {
|
||||
FAIL() << "Couldn't find helper binary";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// mmap two segments out of the helper binary, one
|
||||
// enclosed in the other, but with different protections.
|
||||
const size_t kPageSize = sysconf(_SC_PAGESIZE);
|
||||
const size_t kMappingSize = 3 * kPageSize;
|
||||
int fd = open(helper_path.c_str(), O_RDONLY);
|
||||
ASSERT_NE(-1, fd);
|
||||
char* mapping =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMappingSize,
|
||||
PROT_READ,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
0));
|
||||
ASSERT_TRUE(mapping);
|
||||
|
||||
const u_int64_t kMappingAddress = reinterpret_cast<u_int64_t>(mapping);
|
||||
|
||||
// Ensure that things get cleaned up.
|
||||
StackHelper helper(fd, mapping, kMappingSize);
|
||||
|
||||
// Carve a page out of the first mapping with different permissions.
|
||||
char* inside_mapping = reinterpret_cast<char*>(mmap(mapping + 2 *kPageSize,
|
||||
kPageSize,
|
||||
PROT_NONE,
|
||||
MAP_SHARED | MAP_FIXED,
|
||||
fd,
|
||||
// Map a different offset just to
|
||||
// better test real-world conditions.
|
||||
kPageSize));
|
||||
ASSERT_TRUE(inside_mapping);
|
||||
|
||||
// Now check that LinuxDumper interpreted the mappings properly.
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
int mapping_count = 0;
|
||||
for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
|
||||
const MappingInfo& mapping = *dumper.mappings()[i];
|
||||
if (strcmp(mapping.name, helper_path.c_str()) == 0) {
|
||||
// This mapping should encompass the entire original mapped
|
||||
// range.
|
||||
EXPECT_EQ(kMappingAddress, mapping.start_addr);
|
||||
EXPECT_EQ(kMappingSize, mapping.size);
|
||||
EXPECT_EQ(0, mapping.offset);
|
||||
mapping_count++;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(1, mapping_count);
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
||||
static const int kNumberOfThreadsInHelperProgram = 5;
|
||||
char kNumberOfThreadsArgument[2];
|
||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
// In child process.
|
||||
close(fds[0]);
|
||||
|
||||
string helper_path(GetHelperBinary());
|
||||
if (helper_path.empty()) {
|
||||
FAIL() << "Couldn't find helper binary";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Pass the pipe fd and the number of threads as arguments.
|
||||
char pipe_fd_string[8];
|
||||
sprintf(pipe_fd_string, "%d", fds[1]);
|
||||
execl(helper_path.c_str(),
|
||||
"linux_dumper_unittest_helper",
|
||||
pipe_fd_string,
|
||||
kNumberOfThreadsArgument,
|
||||
NULL);
|
||||
// Kill if we get here.
|
||||
printf("Errno from exec: %d", errno);
|
||||
FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
|
||||
exit(0);
|
||||
}
|
||||
close(fds[1]);
|
||||
// Wait for the child process to signal that it's ready.
|
||||
struct pollfd pfd;
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = fds[0];
|
||||
pfd.events = POLLIN | POLLERR;
|
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
|
||||
ASSERT_EQ(1, r);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
uint8_t junk;
|
||||
read(fds[0], &junk, sizeof(junk));
|
||||
close(fds[0]);
|
||||
|
||||
// Child is ready now.
|
||||
LinuxDumper dumper(child_pid);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
|
||||
EXPECT_TRUE(dumper.ThreadsSuspend());
|
||||
|
||||
ThreadInfo one_thread;
|
||||
for(size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||
EXPECT_TRUE(dumper.ThreadInfoGet(dumper.threads()[i], &one_thread));
|
||||
// In the helper program, we stored a pointer to the thread id in a
|
||||
// specific register. Check that we can recover its value.
|
||||
#if defined(__ARM_EABI__)
|
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]);
|
||||
#elif defined(__i386)
|
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx);
|
||||
#elif defined(__x86_64)
|
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx);
|
||||
#else
|
||||
#error This test has not been ported to this platform.
|
||||
#endif
|
||||
pid_t one_thread_id;
|
||||
dumper.CopyFromProcess(&one_thread_id,
|
||||
dumper.threads()[i],
|
||||
process_tid_location,
|
||||
4);
|
||||
EXPECT_EQ(dumper.threads()[i], one_thread_id);
|
||||
}
|
||||
kill(child_pid, SIGKILL);
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, BuildProcPath) {
|
||||
const pid_t pid = getpid();
|
||||
LinuxDumper dumper(pid);
|
||||
@@ -94,25 +275,155 @@ TEST(LinuxDumperTest, BuildProcPath) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(__ARM_EABI__)
|
||||
// Ensure that the linux-gate VDSO is included in the mapping list.
|
||||
TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
|
||||
if (linux_gate_loc) {
|
||||
bool found_linux_gate = false;
|
||||
ASSERT_TRUE(linux_gate_loc);
|
||||
bool found_linux_gate = false;
|
||||
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
const MappingInfo* mapping;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
mapping = mappings[i];
|
||||
if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
break;
|
||||
}
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
const MappingInfo* mapping;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
mapping = mappings[i];
|
||||
if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
break;
|
||||
}
|
||||
EXPECT_TRUE(found_linux_gate);
|
||||
EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
|
||||
EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
|
||||
}
|
||||
EXPECT_TRUE(found_linux_gate);
|
||||
EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
|
||||
EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
|
||||
}
|
||||
|
||||
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
|
||||
TEST(LinuxDumperTest, LinuxGateMappingID) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
bool found_linux_gate = false;
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
unsigned index = 0;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_linux_gate);
|
||||
|
||||
uint8_t identifier[sizeof(MDGUID)];
|
||||
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
|
||||
true,
|
||||
index,
|
||||
identifier));
|
||||
uint8_t empty_identifier[sizeof(MDGUID)];
|
||||
memset(empty_identifier, 0, sizeof(empty_identifier));
|
||||
EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
|
||||
}
|
||||
|
||||
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
|
||||
// from a child process.
|
||||
TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// Fork a child so ptrace works.
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
// Now wait forever for the parent.
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
LinuxDumper dumper(child);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
bool found_linux_gate = false;
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
unsigned index = 0;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_linux_gate);
|
||||
|
||||
// Need to suspend the child so ptrace actually works.
|
||||
ASSERT_TRUE(dumper.ThreadsSuspend());
|
||||
uint8_t identifier[sizeof(MDGUID)];
|
||||
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
|
||||
true,
|
||||
index,
|
||||
identifier));
|
||||
uint8_t empty_identifier[sizeof(MDGUID)];
|
||||
memset(empty_identifier, 0, sizeof(empty_identifier));
|
||||
EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
|
||||
EXPECT_TRUE(dumper.ThreadsResume());
|
||||
close(fds[1]);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(LinuxDumperTest, FileIDsMatch) {
|
||||
// Calculate the File ID of our binary using both
|
||||
// FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
|
||||
// and ensure that we get the same result from both.
|
||||
char exe_name[PATH_MAX];
|
||||
ssize_t len = readlink("/proc/self/exe", exe_name, PATH_MAX - 1);
|
||||
ASSERT_NE(len, -1);
|
||||
exe_name[len] = '\0';
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// Fork a child so ptrace works.
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
// Now wait forever for the parent.
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
LinuxDumper dumper(child);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
bool found_exe = false;
|
||||
unsigned i;
|
||||
for (i = 0; i < mappings.size(); ++i) {
|
||||
const MappingInfo* mapping = mappings[i];
|
||||
if (!strcmp(mapping->name, exe_name)) {
|
||||
found_exe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_exe);
|
||||
|
||||
uint8_t identifier1[sizeof(MDGUID)];
|
||||
uint8_t identifier2[sizeof(MDGUID)];
|
||||
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
|
||||
identifier1));
|
||||
FileID fileid(exe_name);
|
||||
EXPECT_TRUE(fileid.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);
|
||||
close(fds[1]);
|
||||
}
|
||||
|
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2010, 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.
|
||||
//
|
||||
// Helper program for the linux_dumper class, which creates a bunch of
|
||||
// threads. The first word of each thread's stack is set to the thread
|
||||
// id.
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
#if defined(__ARM_EABI__)
|
||||
#define TID_PTR_REGISTER "r3"
|
||||
#elif defined(__i386)
|
||||
#define TID_PTR_REGISTER "ecx"
|
||||
#elif defined(__x86_64)
|
||||
#define TID_PTR_REGISTER "rcx"
|
||||
#else
|
||||
#error This test has not been ported to this platform.
|
||||
#endif
|
||||
|
||||
void *thread_function(void *data) {
|
||||
volatile pid_t thread_id = syscall(__NR_gettid);
|
||||
register volatile pid_t *thread_id_ptr asm(TID_PTR_REGISTER) = &thread_id;
|
||||
while (true)
|
||||
asm volatile ("" : : "r" (thread_id_ptr));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr,
|
||||
"usage: linux_dumper_unittest_helper <pipe fd> <# of threads\n");
|
||||
return 1;
|
||||
}
|
||||
int pipefd = atoi(argv[1]);
|
||||
int num_threads = atoi(argv[2]);
|
||||
if (num_threads < 1) {
|
||||
fprintf(stderr, "ERROR: number of threads is 0");
|
||||
return 1;
|
||||
}
|
||||
pthread_t threads[num_threads];
|
||||
pthread_attr_t thread_attributes;
|
||||
pthread_attr_init(&thread_attributes);
|
||||
pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED);
|
||||
for (int i = 1; i < num_threads; i++) {
|
||||
pthread_create(&threads[i], &thread_attributes, &thread_function, NULL);
|
||||
}
|
||||
// Signal parent that this process has started all threads.
|
||||
uint8_t byte = 1;
|
||||
write(pipefd, &byte, sizeof(byte));
|
||||
thread_function(NULL);
|
||||
return 0;
|
||||
}
|
75
src/client/linux/minidump_writer/minidump_extension_linux.h
Normal file
75
src/client/linux/minidump_writer/minidump_extension_linux.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/* Copyright (c) 2010, 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. */
|
||||
|
||||
/* minidump_extension_linux.h: A definition of exception codes for
|
||||
* Linux
|
||||
*
|
||||
* (This is C99 source, please don't corrupt it with C++.)
|
||||
*
|
||||
* Author: Adam Langley
|
||||
* Split into its own file: Markus Gutschke */
|
||||
|
||||
|
||||
#ifndef SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_
|
||||
#define SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "google_breakpad/common/breakpad_types.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
// These are additional minidump stream values which are specific to the linux
|
||||
// breakpad implementation.
|
||||
enum {
|
||||
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
|
||||
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
|
||||
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
|
||||
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
|
||||
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
|
||||
MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
|
||||
MD_LINUX_MAPS = 0x47670009, /* /proc/$x/maps */
|
||||
MD_LINUX_DSO_DEBUG = 0x4767000A /* DSO data */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
void* addr;
|
||||
MDRVA name;
|
||||
void* ld;
|
||||
} MDRawLinkMap;
|
||||
|
||||
typedef struct {
|
||||
u_int32_t version;
|
||||
MDRVA map;
|
||||
u_int32_t dso_count;
|
||||
void* brk;
|
||||
void* ldbase;
|
||||
void* dynamic;
|
||||
} MDRawDebug;
|
||||
|
||||
#endif // SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2010, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -48,33 +48,35 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <link.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/ucontext.h>
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "google_breakpad/common/minidump_cpu_amd64.h"
|
||||
#include "google_breakpad/common/minidump_cpu_x86.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "client/linux/android_link.h"
|
||||
#include "client/linux/android_ucontext.h"
|
||||
#endif
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "client/linux/minidump_writer//linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_extension_linux.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/linux_syscall_support.h"
|
||||
|
||||
// These are additional minidump stream values which are specific to the linux
|
||||
// breakpad implementation.
|
||||
enum {
|
||||
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
|
||||
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
|
||||
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
|
||||
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
|
||||
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
|
||||
MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
|
||||
};
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
// Minidump defines register structures which are different from the raw
|
||||
// structures which we get from the kernel. These are platform specific
|
||||
@@ -248,21 +250,21 @@ static void CPUFillFromThreadInfo(MDRawContextAMD64 *out,
|
||||
|
||||
out->flt_save.control_word = info.fpregs.cwd;
|
||||
out->flt_save.status_word = info.fpregs.swd;
|
||||
out->flt_save.tag_word = info.fpregs.twd;
|
||||
out->flt_save.tag_word = info.fpregs.ftw;
|
||||
out->flt_save.error_opcode = info.fpregs.fop;
|
||||
out->flt_save.error_offset = info.fpregs.rip;
|
||||
out->flt_save.error_selector = 0; // We don't have this.
|
||||
out->flt_save.error_selector = 0; // We don't have this.
|
||||
out->flt_save.data_offset = info.fpregs.rdp;
|
||||
out->flt_save.data_selector = 0; // We don't have this.
|
||||
out->flt_save.data_selector = 0; // We don't have this.
|
||||
out->flt_save.mx_csr = info.fpregs.mxcsr;
|
||||
out->flt_save.mx_csr_mask = info.fpregs.mxcsr_mask;
|
||||
out->flt_save.mx_csr_mask = info.fpregs.mxcr_mask;
|
||||
memcpy(&out->flt_save.float_registers, &info.fpregs.st_space, 8 * 16);
|
||||
memcpy(&out->flt_save.xmm_registers, &info.fpregs.xmm_space, 16 * 16);
|
||||
}
|
||||
|
||||
static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc,
|
||||
const struct _libc_fpstate* fpregs) {
|
||||
const greg_t* regs = uc->gregs;
|
||||
const greg_t* regs = uc->uc_mcontext.gregs;
|
||||
|
||||
out->context_flags = MD_CONTEXT_AMD64_FULL;
|
||||
|
||||
@@ -299,14 +301,64 @@ static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc,
|
||||
out->flt_save.error_opcode = fpregs->fop;
|
||||
out->flt_save.error_offset = fpregs->rip;
|
||||
out->flt_save.data_offset = fpregs->rdp;
|
||||
out->flt_save.error_selector = 0; // We don't have this.
|
||||
out->flt_save.error_selector = 0; // We don't have this.
|
||||
out->flt_save.data_selector = 0; // We don't have this.
|
||||
out->flt_save.mx_csr = fpregs->mxcsr;
|
||||
out->flt_save.mx_csr_mask = fpregs->mxcsr_mask;
|
||||
out->flt_save.mx_csr_mask = fpregs->mxcr_mask;
|
||||
memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
|
||||
memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
|
||||
}
|
||||
|
||||
#elif defined(__ARMEL__)
|
||||
typedef MDRawContextARM RawContextCPU;
|
||||
|
||||
static void CPUFillFromThreadInfo(MDRawContextARM *out,
|
||||
const google_breakpad::ThreadInfo &info) {
|
||||
out->context_flags = MD_CONTEXT_ARM_FULL;
|
||||
|
||||
for (int i = 0; i < MD_CONTEXT_ARM_GPR_COUNT; ++i)
|
||||
out->iregs[i] = info.regs.uregs[i];
|
||||
// No CPSR register in ThreadInfo(it's not accessible via ptrace)
|
||||
out->cpsr = 0;
|
||||
#if !defined(__ANDROID__)
|
||||
out->float_save.fpscr = info.fpregs.fpsr |
|
||||
(static_cast<u_int64_t>(info.fpregs.fpcr) << 32);
|
||||
// TODO: sort this out, actually collect floating point registers
|
||||
memset(&out->float_save.regs, 0, sizeof(out->float_save.regs));
|
||||
memset(&out->float_save.extra, 0, sizeof(out->float_save.extra));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void CPUFillFromUContext(MDRawContextARM *out, const ucontext *uc,
|
||||
const struct _libc_fpstate* fpregs) {
|
||||
out->context_flags = MD_CONTEXT_ARM_FULL;
|
||||
|
||||
out->iregs[0] = uc->uc_mcontext.arm_r0;
|
||||
out->iregs[1] = uc->uc_mcontext.arm_r1;
|
||||
out->iregs[2] = uc->uc_mcontext.arm_r2;
|
||||
out->iregs[3] = uc->uc_mcontext.arm_r3;
|
||||
out->iregs[4] = uc->uc_mcontext.arm_r4;
|
||||
out->iregs[5] = uc->uc_mcontext.arm_r5;
|
||||
out->iregs[6] = uc->uc_mcontext.arm_r6;
|
||||
out->iregs[7] = uc->uc_mcontext.arm_r7;
|
||||
out->iregs[8] = uc->uc_mcontext.arm_r8;
|
||||
out->iregs[9] = uc->uc_mcontext.arm_r9;
|
||||
out->iregs[10] = uc->uc_mcontext.arm_r10;
|
||||
|
||||
out->iregs[11] = uc->uc_mcontext.arm_fp;
|
||||
out->iregs[12] = uc->uc_mcontext.arm_ip;
|
||||
out->iregs[13] = uc->uc_mcontext.arm_sp;
|
||||
out->iregs[14] = uc->uc_mcontext.arm_lr;
|
||||
out->iregs[15] = uc->uc_mcontext.arm_pc;
|
||||
|
||||
out->cpsr = uc->uc_mcontext.arm_cpsr;
|
||||
|
||||
// TODO: fix this after fixing ExceptionHandler
|
||||
out->float_save.fpscr = 0;
|
||||
memset(&out->float_save.regs, 0, sizeof(out->float_save.regs));
|
||||
memset(&out->float_save.extra, 0, sizeof(out->float_save.extra));
|
||||
}
|
||||
|
||||
#else
|
||||
#error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
@@ -317,13 +369,21 @@ class MinidumpWriter {
|
||||
public:
|
||||
MinidumpWriter(const char* filename,
|
||||
pid_t crashing_pid,
|
||||
const ExceptionHandler::CrashContext* context)
|
||||
const ExceptionHandler::CrashContext* context,
|
||||
const MappingList& mappings)
|
||||
: filename_(filename),
|
||||
siginfo_(&context->siginfo),
|
||||
ucontext_(&context->context),
|
||||
#if !defined(__ARM_EABI__)
|
||||
float_state_(&context->float_state),
|
||||
#else
|
||||
// TODO: fix this after fixing ExceptionHandler
|
||||
float_state_(NULL),
|
||||
#endif
|
||||
crashing_tid_(context->tid),
|
||||
dumper_(crashing_pid) {
|
||||
dumper_(crashing_pid),
|
||||
memory_blocks_(dumper_.allocator()),
|
||||
mapping_list_(mappings) {
|
||||
}
|
||||
|
||||
bool Init() {
|
||||
@@ -337,9 +397,32 @@ class MinidumpWriter {
|
||||
}
|
||||
|
||||
bool Dump() {
|
||||
// The dynamic linker makes information available that helps gdb find all
|
||||
// DSOs loaded into the program. If we can access this information, we dump
|
||||
// it to a MD_LINUX_DSO_DEBUG stream.
|
||||
struct r_debug* r_debug = NULL;
|
||||
uint32_t dynamic_length = 0;
|
||||
#if !defined(__ANDROID__)
|
||||
// The Android NDK is missing structure definitions for most of this.
|
||||
// For now, it's simpler just to skip it.
|
||||
for (int i = 0;;) {
|
||||
ElfW(Dyn) dyn;
|
||||
dynamic_length += sizeof(dyn);
|
||||
dumper_.CopyFromProcess(&dyn, crashing_tid_, _DYNAMIC+i++, sizeof(dyn));
|
||||
if (dyn.d_tag == DT_DEBUG) {
|
||||
r_debug = (struct r_debug*)dyn.d_un.d_ptr;
|
||||
continue;
|
||||
} else if (dyn.d_tag == DT_NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// A minidump file contains a number of tagged streams. This is the number
|
||||
// of stream which we write.
|
||||
static const unsigned kNumWriters = 11;
|
||||
unsigned kNumWriters = 12;
|
||||
if (r_debug)
|
||||
++kNumWriters;
|
||||
|
||||
TypedMDRVA<MDRawHeader> header(&minidump_writer_);
|
||||
TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
|
||||
@@ -366,6 +449,10 @@ class MinidumpWriter {
|
||||
return false;
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
if (!WriteMemoryListStream(&dirent))
|
||||
return false;
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
if (!WriteExceptionStream(&dirent))
|
||||
return false;
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
@@ -404,11 +491,18 @@ class MinidumpWriter {
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_AUXV;
|
||||
dirent.stream_type = MD_LINUX_MAPS;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
if (r_debug) {
|
||||
dirent.stream_type = MD_LINUX_DSO_DEBUG;
|
||||
if (!WriteDSODebugStream(&dirent, r_debug, dynamic_length))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
}
|
||||
|
||||
// If you add more directory entries, don't forget to update kNumWriters,
|
||||
// above.
|
||||
|
||||
@@ -416,6 +510,123 @@ class MinidumpWriter {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the top of the stack is part of a system call that has been
|
||||
// redirected by the seccomp sandbox. If so, try to pop the stack frames
|
||||
// all the way back to the point where the interception happened.
|
||||
void PopSeccompStackFrame(RawContextCPU* cpu, const MDRawThread& thread,
|
||||
uint8_t* stack_copy) {
|
||||
#if defined(__x86_64)
|
||||
u_int64_t bp = cpu->rbp;
|
||||
u_int64_t top = thread.stack.start_of_memory_range;
|
||||
for (int i = 4; i--; ) {
|
||||
if (bp < top ||
|
||||
bp + sizeof(bp) > thread.stack.start_of_memory_range +
|
||||
thread.stack.memory.data_size ||
|
||||
bp & 1) {
|
||||
break;
|
||||
}
|
||||
uint64_t old_top = top;
|
||||
top = bp;
|
||||
u_int8_t* bp_addr = stack_copy + bp - thread.stack.start_of_memory_range;
|
||||
memcpy(&bp, bp_addr, sizeof(bp));
|
||||
if (bp == 0xDEADBEEFDEADBEEFull) {
|
||||
struct {
|
||||
uint64_t r15;
|
||||
uint64_t r14;
|
||||
uint64_t r13;
|
||||
uint64_t r12;
|
||||
uint64_t r11;
|
||||
uint64_t r10;
|
||||
uint64_t r9;
|
||||
uint64_t r8;
|
||||
uint64_t rdi;
|
||||
uint64_t rsi;
|
||||
uint64_t rdx;
|
||||
uint64_t rcx;
|
||||
uint64_t rbx;
|
||||
uint64_t deadbeef;
|
||||
uint64_t rbp;
|
||||
uint64_t fakeret;
|
||||
uint64_t ret;
|
||||
/* char redzone[128]; */
|
||||
} seccomp_stackframe;
|
||||
if (top - offsetof(typeof(seccomp_stackframe), deadbeef) < old_top ||
|
||||
top - offsetof(typeof(seccomp_stackframe), deadbeef) +
|
||||
sizeof(seccomp_stackframe) >
|
||||
thread.stack.start_of_memory_range+thread.stack.memory.data_size) {
|
||||
break;
|
||||
}
|
||||
memcpy(&seccomp_stackframe,
|
||||
bp_addr - offsetof(typeof(seccomp_stackframe), deadbeef),
|
||||
sizeof(seccomp_stackframe));
|
||||
cpu->rbx = seccomp_stackframe.rbx;
|
||||
cpu->rcx = seccomp_stackframe.rcx;
|
||||
cpu->rdx = seccomp_stackframe.rdx;
|
||||
cpu->rsi = seccomp_stackframe.rsi;
|
||||
cpu->rdi = seccomp_stackframe.rdi;
|
||||
cpu->rbp = seccomp_stackframe.rbp;
|
||||
cpu->rsp = top + 4*sizeof(uint64_t) + 128;
|
||||
cpu->r8 = seccomp_stackframe.r8;
|
||||
cpu->r9 = seccomp_stackframe.r9;
|
||||
cpu->r10 = seccomp_stackframe.r10;
|
||||
cpu->r11 = seccomp_stackframe.r11;
|
||||
cpu->r12 = seccomp_stackframe.r12;
|
||||
cpu->r13 = seccomp_stackframe.r13;
|
||||
cpu->r14 = seccomp_stackframe.r14;
|
||||
cpu->r15 = seccomp_stackframe.r15;
|
||||
cpu->rip = seccomp_stackframe.fakeret;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#elif defined(__i386)
|
||||
u_int32_t bp = cpu->ebp;
|
||||
u_int32_t top = thread.stack.start_of_memory_range;
|
||||
for (int i = 4; i--; ) {
|
||||
if (bp < top ||
|
||||
bp + sizeof(bp) > thread.stack.start_of_memory_range +
|
||||
thread.stack.memory.data_size ||
|
||||
bp & 1) {
|
||||
break;
|
||||
}
|
||||
uint32_t old_top = top;
|
||||
top = bp;
|
||||
u_int8_t* bp_addr = stack_copy + bp - thread.stack.start_of_memory_range;
|
||||
memcpy(&bp, bp_addr, sizeof(bp));
|
||||
if (bp == 0xDEADBEEFu) {
|
||||
struct {
|
||||
uint32_t edi;
|
||||
uint32_t esi;
|
||||
uint32_t edx;
|
||||
uint32_t ecx;
|
||||
uint32_t ebx;
|
||||
uint32_t deadbeef;
|
||||
uint32_t ebp;
|
||||
uint32_t fakeret;
|
||||
uint32_t ret;
|
||||
} seccomp_stackframe;
|
||||
if (top - offsetof(typeof(seccomp_stackframe), deadbeef) < old_top ||
|
||||
top - offsetof(typeof(seccomp_stackframe), deadbeef) +
|
||||
sizeof(seccomp_stackframe) >
|
||||
thread.stack.start_of_memory_range+thread.stack.memory.data_size) {
|
||||
break;
|
||||
}
|
||||
memcpy(&seccomp_stackframe,
|
||||
bp_addr - offsetof(typeof(seccomp_stackframe), deadbeef),
|
||||
sizeof(seccomp_stackframe));
|
||||
cpu->ebx = seccomp_stackframe.ebx;
|
||||
cpu->ecx = seccomp_stackframe.ecx;
|
||||
cpu->edx = seccomp_stackframe.edx;
|
||||
cpu->esi = seccomp_stackframe.esi;
|
||||
cpu->edi = seccomp_stackframe.edi;
|
||||
cpu->ebp = seccomp_stackframe.ebp;
|
||||
cpu->esp = top + 4*sizeof(void*);
|
||||
cpu->eip = seccomp_stackframe.fakeret;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Write information about the threads.
|
||||
bool WriteThreadListStream(MDRawDirectory* dirent) {
|
||||
const unsigned num_threads = dumper_.threads().size();
|
||||
@@ -437,7 +648,7 @@ class MinidumpWriter {
|
||||
// we used the actual state of the thread we would find it running in the
|
||||
// signal handler with the alternative stack, which would be deeply
|
||||
// unhelpful.
|
||||
if (thread.thread_id == crashing_tid_) {
|
||||
if ((pid_t)thread.thread_id == crashing_tid_) {
|
||||
const void* stack;
|
||||
size_t stack_len;
|
||||
if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer()))
|
||||
@@ -450,11 +661,57 @@ class MinidumpWriter {
|
||||
memory.Copy(stack_copy, stack_len);
|
||||
thread.stack.start_of_memory_range = (uintptr_t) (stack);
|
||||
thread.stack.memory = memory.location();
|
||||
memory_blocks_.push_back(thread.stack);
|
||||
|
||||
// Copy 256 bytes around crashing instruction pointer to minidump.
|
||||
const size_t kIPMemorySize = 256;
|
||||
u_int64_t ip = GetInstructionPointer();
|
||||
// Bound it to the upper and lower bounds of the memory map
|
||||
// it's contained within. If it's not in mapped memory,
|
||||
// don't bother trying to write it.
|
||||
bool ip_is_mapped = false;
|
||||
MDMemoryDescriptor ip_memory_d;
|
||||
for (unsigned j = 0; j < dumper_.mappings().size(); ++j) {
|
||||
const MappingInfo& mapping = *dumper_.mappings()[j];
|
||||
if (ip >= mapping.start_addr &&
|
||||
ip < mapping.start_addr + mapping.size) {
|
||||
ip_is_mapped = true;
|
||||
// Try to get 128 bytes before and after the IP, but
|
||||
// settle for whatever's available.
|
||||
ip_memory_d.start_of_memory_range =
|
||||
std::max(mapping.start_addr,
|
||||
uintptr_t(ip - (kIPMemorySize / 2)));
|
||||
uintptr_t end_of_range =
|
||||
std::min(uintptr_t(ip + (kIPMemorySize / 2)),
|
||||
uintptr_t(mapping.start_addr + mapping.size));
|
||||
ip_memory_d.memory.data_size =
|
||||
end_of_range - ip_memory_d.start_of_memory_range;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ip_is_mapped) {
|
||||
UntypedMDRVA ip_memory(&minidump_writer_);
|
||||
if (!ip_memory.Allocate(ip_memory_d.memory.data_size))
|
||||
return false;
|
||||
uint8_t* memory_copy =
|
||||
(uint8_t*) dumper_.allocator()->Alloc(ip_memory_d.memory.data_size);
|
||||
dumper_.CopyFromProcess(
|
||||
memory_copy,
|
||||
thread.thread_id,
|
||||
reinterpret_cast<void*>(ip_memory_d.start_of_memory_range),
|
||||
ip_memory_d.memory.data_size);
|
||||
ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size);
|
||||
ip_memory_d.memory = ip_memory.location();
|
||||
memory_blocks_.push_back(ip_memory_d);
|
||||
}
|
||||
|
||||
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
|
||||
if (!cpu.Allocate())
|
||||
return false;
|
||||
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||
CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
|
||||
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
|
||||
thread.thread_context = cpu.location();
|
||||
crashing_thread_context_ = cpu.location();
|
||||
} else {
|
||||
@@ -471,11 +728,14 @@ class MinidumpWriter {
|
||||
memory.Copy(stack_copy, info.stack_len);
|
||||
thread.stack.start_of_memory_range = (uintptr_t)(info.stack);
|
||||
thread.stack.memory = memory.location();
|
||||
memory_blocks_.push_back(thread.stack);
|
||||
|
||||
TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
|
||||
if (!cpu.Allocate())
|
||||
return false;
|
||||
my_memset(cpu.get(), 0, sizeof(RawContextCPU));
|
||||
CPUFillFromThreadInfo(cpu.get(), info);
|
||||
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
|
||||
thread.thread_context = cpu.location();
|
||||
}
|
||||
|
||||
@@ -486,8 +746,8 @@ class MinidumpWriter {
|
||||
}
|
||||
|
||||
static bool ShouldIncludeMapping(const MappingInfo& mapping) {
|
||||
if (mapping.name[0] == 0 || // we only want modules with filenames.
|
||||
mapping.offset || // we only want to include one mapping per shared lib.
|
||||
if (mapping.name[0] == 0 || // only want modules with filenames.
|
||||
mapping.offset || // only want to include one mapping per shared lib.
|
||||
mapping.size < 4096) { // too small to get a signature for.
|
||||
return false;
|
||||
}
|
||||
@@ -495,96 +755,147 @@ class MinidumpWriter {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there is caller-provided information about this mapping
|
||||
// in the mapping_list_ list, return true. Otherwise, return false.
|
||||
bool HaveMappingInfo(const MappingInfo& mapping) {
|
||||
for (MappingList::const_iterator iter = mapping_list_.begin();
|
||||
iter != mapping_list_.end();
|
||||
++iter) {
|
||||
// Ignore any mappings that are wholly contained within
|
||||
// mappings in the mapping_info_ list.
|
||||
if (mapping.start_addr >= iter->first.start_addr &&
|
||||
(mapping.start_addr + mapping.size) <=
|
||||
(iter->first.start_addr + iter->first.size)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write information about the mappings in effect. Because we are using the
|
||||
// minidump format, the information about the mappings is pretty limited.
|
||||
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
|
||||
// another stream in the file.
|
||||
bool WriteMappings(MDRawDirectory* dirent) {
|
||||
const unsigned num_mappings = dumper_.mappings().size();
|
||||
unsigned num_output_mappings = 0;
|
||||
unsigned num_output_mappings = mapping_list_.size();
|
||||
|
||||
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
|
||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||
if (ShouldIncludeMapping(mapping))
|
||||
if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping))
|
||||
num_output_mappings++;
|
||||
}
|
||||
|
||||
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||
if (!list.AllocateObjectAndArray(num_output_mappings, sizeof(MDRawModule)))
|
||||
if (!list.AllocateObjectAndArray(num_output_mappings, MD_MODULE_SIZE))
|
||||
return false;
|
||||
|
||||
dirent->stream_type = MD_MODULE_LIST_STREAM;
|
||||
dirent->location = list.location();
|
||||
*list.get() = num_output_mappings;
|
||||
|
||||
for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
|
||||
// First write all the mappings from the dumper
|
||||
unsigned int j = 0;
|
||||
for (unsigned i = 0; i < num_mappings; ++i) {
|
||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||
if (!ShouldIncludeMapping(mapping))
|
||||
if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping))
|
||||
continue;
|
||||
|
||||
MDRawModule mod;
|
||||
my_memset(&mod, 0, sizeof(mod));
|
||||
mod.base_of_image = mapping.start_addr;
|
||||
mod.size_of_image = mapping.size;
|
||||
const size_t filepath_len = my_strlen(mapping.name);
|
||||
|
||||
// Figure out file name from path
|
||||
const char* filename_ptr = mapping.name + filepath_len - 1;
|
||||
while (filename_ptr >= mapping.name) {
|
||||
if (*filename_ptr == '/')
|
||||
break;
|
||||
filename_ptr--;
|
||||
}
|
||||
filename_ptr++;
|
||||
const size_t filename_len = mapping.name + filepath_len - filename_ptr;
|
||||
|
||||
uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
|
||||
uint8_t* cv_ptr = cv_buf;
|
||||
UntypedMDRVA cv(&minidump_writer_);
|
||||
if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
|
||||
if (!FillRawModule(mapping, true, i, mod, NULL))
|
||||
return false;
|
||||
|
||||
const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||
memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
|
||||
cv_ptr += sizeof(cv_signature);
|
||||
|
||||
{
|
||||
// We XOR the first page of the file to get a signature for it.
|
||||
uint8_t xor_buf[sizeof(MDGUID)];
|
||||
size_t done = 0;
|
||||
uint8_t* signature = cv_ptr;
|
||||
cv_ptr += sizeof(xor_buf);
|
||||
|
||||
my_memset(signature, 0, sizeof(xor_buf));
|
||||
while (done < 4096) {
|
||||
dumper_.CopyFromProcess(xor_buf, crashing_tid_,
|
||||
(void *) (mod.base_of_image + done),
|
||||
sizeof(xor_buf));
|
||||
for (unsigned i = 0; i < sizeof(xor_buf); ++i)
|
||||
signature[i] ^= xor_buf[i];
|
||||
done += sizeof(xor_buf);
|
||||
}
|
||||
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
|
||||
cv_ptr += sizeof(uint32_t);
|
||||
}
|
||||
|
||||
// Write pdb_file_name
|
||||
memcpy(cv_ptr, filename_ptr, filename_len + 1);
|
||||
cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
|
||||
|
||||
mod.cv_record = cv.location();
|
||||
|
||||
MDLocationDescriptor ld;
|
||||
if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
|
||||
list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
|
||||
}
|
||||
// Next write all the mappings provided by the caller
|
||||
for (MappingList::const_iterator iter = mapping_list_.begin();
|
||||
iter != mapping_list_.end();
|
||||
++iter) {
|
||||
MDRawModule mod;
|
||||
if (!FillRawModule(iter->first, false, 0, mod, iter->second))
|
||||
return false;
|
||||
mod.module_name_rva = ld.rva;
|
||||
|
||||
list.CopyIndexAfterObject(j++, &mod, sizeof(mod));
|
||||
list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fill the MDRawModule |mod| with information about the provided
|
||||
// |mapping|. If |identifier| is non-NULL, use it instead of calculating
|
||||
// a file ID from the mapping.
|
||||
bool FillRawModule(const MappingInfo& mapping,
|
||||
bool member,
|
||||
unsigned int mapping_id,
|
||||
MDRawModule& mod,
|
||||
const u_int8_t* identifier) {
|
||||
my_memset(&mod, 0, MD_MODULE_SIZE);
|
||||
|
||||
mod.base_of_image = mapping.start_addr;
|
||||
mod.size_of_image = mapping.size;
|
||||
const size_t filepath_len = my_strlen(mapping.name);
|
||||
|
||||
// Figure out file name from path
|
||||
const char* filename_ptr = mapping.name + filepath_len - 1;
|
||||
while (filename_ptr >= mapping.name) {
|
||||
if (*filename_ptr == '/')
|
||||
break;
|
||||
filename_ptr--;
|
||||
}
|
||||
filename_ptr++;
|
||||
|
||||
const size_t filename_len = mapping.name + filepath_len - filename_ptr;
|
||||
|
||||
uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
|
||||
uint8_t* cv_ptr = cv_buf;
|
||||
UntypedMDRVA cv(&minidump_writer_);
|
||||
if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
|
||||
return false;
|
||||
|
||||
const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||
memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
|
||||
cv_ptr += sizeof(cv_signature);
|
||||
uint8_t* signature = cv_ptr;
|
||||
cv_ptr += sizeof(MDGUID);
|
||||
if (identifier) {
|
||||
// GUID was provided by caller.
|
||||
memcpy(signature, identifier, sizeof(MDGUID));
|
||||
} else {
|
||||
dumper_.ElfFileIdentifierForMapping(mapping, member,
|
||||
mapping_id, signature);
|
||||
}
|
||||
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
|
||||
cv_ptr += sizeof(uint32_t);
|
||||
|
||||
// Write pdb_file_name
|
||||
memcpy(cv_ptr, filename_ptr, filename_len + 1);
|
||||
cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
|
||||
|
||||
mod.cv_record = cv.location();
|
||||
|
||||
MDLocationDescriptor ld;
|
||||
if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
|
||||
return false;
|
||||
mod.module_name_rva = ld.rva;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteMemoryListStream(MDRawDirectory* dirent) {
|
||||
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||
if (!list.AllocateObjectAndArray(memory_blocks_.size(),
|
||||
sizeof(MDMemoryDescriptor)))
|
||||
return false;
|
||||
|
||||
dirent->stream_type = MD_MEMORY_LIST_STREAM;
|
||||
dirent->location = list.location();
|
||||
|
||||
*list.get() = memory_blocks_.size();
|
||||
|
||||
for (size_t i = 0; i < memory_blocks_.size(); ++i) {
|
||||
list.CopyIndexAfterObject(i, &memory_blocks_[i],
|
||||
sizeof(MDMemoryDescriptor));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteExceptionStream(MDRawDirectory* dirent) {
|
||||
TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
|
||||
if (!exc.Allocate())
|
||||
@@ -618,15 +929,112 @@ class MinidumpWriter {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteDSODebugStream(MDRawDirectory* dirent, struct r_debug* r_debug,
|
||||
uint32_t dynamic_length) {
|
||||
#if defined(__ANDROID__)
|
||||
return false;
|
||||
#else
|
||||
// The caller provided us with a pointer to "struct r_debug". We can
|
||||
// look up the "r_map" field to get a linked list of all loaded DSOs.
|
||||
// Our list of DSOs potentially is different from the ones in the crashing
|
||||
// process. So, we have to be careful to never dereference pointers
|
||||
// directly. Instead, we use CopyFromProcess() everywhere.
|
||||
// See <link.h> for a more detailed discussion of the how the dynamic
|
||||
// loader communicates with debuggers.
|
||||
|
||||
// Count the number of loaded DSOs
|
||||
int dso_count = 0;
|
||||
struct r_debug debug_entry;
|
||||
dumper_.CopyFromProcess(&debug_entry, crashing_tid_, r_debug,
|
||||
sizeof(debug_entry));
|
||||
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
|
||||
struct link_map map;
|
||||
dumper_.CopyFromProcess(&map, crashing_tid_, ptr, sizeof(map));
|
||||
ptr = map.l_next;
|
||||
dso_count++;
|
||||
}
|
||||
|
||||
MDRVA linkmap_rva = minidump_writer_.kInvalidMDRVA;
|
||||
if (dso_count > 0) {
|
||||
// If we have at least one DSO, create an array of MDRawLinkMap
|
||||
// entries in the minidump file.
|
||||
TypedMDRVA<MDRawLinkMap> linkmap(&minidump_writer_);
|
||||
if (!linkmap.AllocateArray(dso_count))
|
||||
return false;
|
||||
linkmap_rva = linkmap.location().rva;
|
||||
int idx = 0;
|
||||
|
||||
// Iterate over DSOs and write their information to mini dump
|
||||
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
|
||||
struct link_map map;
|
||||
dumper_.CopyFromProcess(&map, crashing_tid_, ptr, sizeof(map));
|
||||
ptr = map.l_next;
|
||||
char filename[257] = { 0 };
|
||||
if (map.l_name) {
|
||||
dumper_.CopyFromProcess(filename, crashing_tid_, map.l_name,
|
||||
sizeof(filename) - 1);
|
||||
}
|
||||
MDLocationDescriptor location;
|
||||
if (!minidump_writer_.WriteString(filename, 0, &location))
|
||||
return false;
|
||||
MDRawLinkMap entry;
|
||||
entry.name = location.rva;
|
||||
entry.addr = (void*)map.l_addr;
|
||||
entry.ld = (void*)map.l_ld;
|
||||
linkmap.CopyIndex(idx++, &entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Write MD_LINUX_DSO_DEBUG record
|
||||
TypedMDRVA<MDRawDebug> debug(&minidump_writer_);
|
||||
if (!debug.AllocateObjectAndArray(1, dynamic_length))
|
||||
return false;
|
||||
my_memset(debug.get(), 0, sizeof(MDRawDebug));
|
||||
dirent->stream_type = MD_LINUX_DSO_DEBUG;
|
||||
dirent->location = debug.location();
|
||||
|
||||
debug.get()->version = debug_entry.r_version;
|
||||
debug.get()->map = linkmap_rva;
|
||||
debug.get()->dso_count = dso_count;
|
||||
debug.get()->brk = (void*)debug_entry.r_brk;
|
||||
debug.get()->ldbase = (void*)debug_entry.r_ldbase;
|
||||
debug.get()->dynamic = (void*)&_DYNAMIC;
|
||||
|
||||
char *dso_debug_data = new char[dynamic_length];
|
||||
dumper_.CopyFromProcess(dso_debug_data, crashing_tid_, &_DYNAMIC,
|
||||
dynamic_length);
|
||||
debug.CopyIndexAfterObject(0, dso_debug_data, dynamic_length);
|
||||
delete[] dso_debug_data;
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(__i386)
|
||||
uintptr_t GetStackPointer() {
|
||||
return ucontext_->uc_mcontext.gregs[REG_ESP];
|
||||
}
|
||||
|
||||
uintptr_t GetInstructionPointer() {
|
||||
return ucontext_->uc_mcontext.gregs[REG_EIP];
|
||||
}
|
||||
#elif defined(__x86_64)
|
||||
uintptr_t GetStackPointer() {
|
||||
return ucontext_->uc_mcontext.gregs[REG_RSP];
|
||||
}
|
||||
|
||||
uintptr_t GetInstructionPointer() {
|
||||
return ucontext_->uc_mcontext.gregs[REG_RIP];
|
||||
}
|
||||
#elif defined(__ARM_EABI__)
|
||||
uintptr_t GetStackPointer() {
|
||||
return ucontext_->uc_mcontext.arm_sp;
|
||||
}
|
||||
|
||||
uintptr_t GetInstructionPointer() {
|
||||
return ucontext_->uc_mcontext.arm_ip;
|
||||
}
|
||||
#else
|
||||
#error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
@@ -650,7 +1058,7 @@ class MinidumpWriter {
|
||||
{ "processor", -1, false },
|
||||
{ "model", 0, false },
|
||||
{ "stepping", 0, false },
|
||||
{ "cpuid level", 0, false },
|
||||
{ "cpu family", 0, false },
|
||||
};
|
||||
|
||||
// processor_architecture should always be set, do this first
|
||||
@@ -659,6 +1067,8 @@ class MinidumpWriter {
|
||||
MD_CPU_ARCHITECTURE_X86;
|
||||
#elif defined(__x86_64)
|
||||
MD_CPU_ARCHITECTURE_AMD64;
|
||||
#elif defined(__arm__)
|
||||
MD_CPU_ARCHITECTURE_ARM;
|
||||
#else
|
||||
#error "Unknown CPU arch"
|
||||
#endif
|
||||
@@ -677,7 +1087,7 @@ class MinidumpWriter {
|
||||
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
|
||||
i++) {
|
||||
CpuInfoEntry* entry = &cpu_info_table[i];
|
||||
if (entry->found)
|
||||
if (entry->found && i)
|
||||
continue;
|
||||
if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
|
||||
const char* value = strchr(line, ':');
|
||||
@@ -726,7 +1136,7 @@ class MinidumpWriter {
|
||||
}
|
||||
}
|
||||
|
||||
popline:
|
||||
popline:
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
sys_close(fd);
|
||||
@@ -763,29 +1173,57 @@ popline:
|
||||
// We can't stat the files because several of the files that we want to
|
||||
// read are kernel seqfiles, which always have a length of zero. So we have
|
||||
// to read as much as we can into a buffer.
|
||||
static const unsigned kMaxFileSize = 1024;
|
||||
uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
|
||||
static const unsigned kBufSize = 1024 - 2*sizeof(void*);
|
||||
struct Buffers {
|
||||
struct Buffers* next;
|
||||
size_t len;
|
||||
uint8_t data[kBufSize];
|
||||
} *buffers =
|
||||
(struct Buffers*) dumper_.allocator()->Alloc(sizeof(struct Buffers));
|
||||
buffers->next = NULL;
|
||||
buffers->len = 0;
|
||||
|
||||
size_t done = 0;
|
||||
while (done < kMaxFileSize) {
|
||||
size_t total = 0;
|
||||
for (struct Buffers* bufptr = buffers;;) {
|
||||
ssize_t r;
|
||||
do {
|
||||
r = sys_read(fd, data + done, kMaxFileSize - done);
|
||||
r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
if (r < 1)
|
||||
break;
|
||||
done += r;
|
||||
|
||||
total += r;
|
||||
bufptr->len += r;
|
||||
if (bufptr->len == kBufSize) {
|
||||
bufptr->next =
|
||||
(struct Buffers*) dumper_.allocator()->Alloc(sizeof(struct Buffers));
|
||||
bufptr = bufptr->next;
|
||||
bufptr->next = NULL;
|
||||
bufptr->len = 0;
|
||||
}
|
||||
}
|
||||
sys_close(fd);
|
||||
|
||||
if (!done)
|
||||
if (!total)
|
||||
return false;
|
||||
|
||||
UntypedMDRVA memory(&minidump_writer_);
|
||||
if (!memory.Allocate(done))
|
||||
if (!memory.Allocate(total))
|
||||
return false;
|
||||
memory.Copy(data, done);
|
||||
for (MDRVA pos = memory.position(); buffers; buffers = buffers->next) {
|
||||
// Check for special case of a zero-length buffer. This should only
|
||||
// occur if a file's size happens to be a multiple of the buffer's
|
||||
// size, in which case the final sys_read() will have resulted in
|
||||
// zero bytes being read after the final buffer was just allocated.
|
||||
if (buffers->len == 0) {
|
||||
// This can only occur with final buffer.
|
||||
assert(buffers->next == NULL);
|
||||
continue;
|
||||
}
|
||||
memory.Copy(pos, &buffers->data, buffers->len);
|
||||
pos += buffers->len;
|
||||
}
|
||||
*result = memory.location();
|
||||
return true;
|
||||
}
|
||||
@@ -838,12 +1276,8 @@ popline:
|
||||
|
||||
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
|
||||
const char* filename) {
|
||||
char buf[80];
|
||||
memcpy(buf, "/proc/", 6);
|
||||
const unsigned pid_len = my_int_len(pid);
|
||||
my_itos(buf + 6, pid, pid_len);
|
||||
buf[6 + pid_len] = '/';
|
||||
memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1);
|
||||
char buf[NAME_MAX];
|
||||
dumper_.BuildProcPath(buf, pid, filename);
|
||||
return WriteFile(result, buf);
|
||||
}
|
||||
|
||||
@@ -855,15 +1289,28 @@ popline:
|
||||
LinuxDumper dumper_;
|
||||
MinidumpFileWriter minidump_writer_;
|
||||
MDLocationDescriptor crashing_thread_context_;
|
||||
// Blocks of memory written to the dump. These are all currently
|
||||
// written while writing the thread list stream, but saved here
|
||||
// so a memory list stream can be written afterwards.
|
||||
wasteful_vector<MDMemoryDescriptor> memory_blocks_;
|
||||
// Additional information about some mappings provided by the caller.
|
||||
const MappingList& mapping_list_;
|
||||
};
|
||||
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size) {
|
||||
MappingList m;
|
||||
return WriteMinidump(filename, crashing_process, blob, blob_size, m);
|
||||
}
|
||||
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings) {
|
||||
if (blob_size != sizeof(ExceptionHandler::CrashContext))
|
||||
return false;
|
||||
const ExceptionHandler::CrashContext* context =
|
||||
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
|
||||
MinidumpWriter writer(filename, crashing_process, context);
|
||||
MinidumpWriter writer(filename, crashing_process, context, mappings);
|
||||
if (!writer.Init())
|
||||
return false;
|
||||
return writer.Dump();
|
||||
|
@@ -33,8 +33,17 @@
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// A list of <MappingInfo, GUID>
|
||||
typedef std::pair<struct MappingInfo, u_int8_t[sizeof(MDGUID)]> MappingEntry;
|
||||
typedef std::list<MappingEntry> MappingList;
|
||||
|
||||
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||
// libc functions which may. Thus, it can be used in contexts where the state
|
||||
// of the heap may be corrupt.
|
||||
@@ -48,6 +57,11 @@ namespace google_breakpad {
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size);
|
||||
|
||||
// This overload also allows passing a list of known mappings.
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2009, Google Inc.
|
||||
// Copyright (c) 2011 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -27,24 +27,34 @@
|
||||
// (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 <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
// This provides a wrapper around system calls which may be
|
||||
// interrupted by a signal and return EINTR. See man 7 signal.
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
typeof(x) __eintr_result__; \
|
||||
do { \
|
||||
__eintr_result__ = x; \
|
||||
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||
__eintr_result__;\
|
||||
})
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
// Length of a formatted GUID string =
|
||||
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||
const int kGUIDStringSize = 37;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test MinidumpWriterTest;
|
||||
@@ -67,8 +77,10 @@ TEST(MinidumpWriterTest, Setup) {
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1;
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
@@ -77,3 +89,308 @@ TEST(MinidumpWriterTest, Setup) {
|
||||
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
// Test that mapping info can be specified when writing a minidump,
|
||||
// and that it ends up in the module list of the minidump.
|
||||
TEST(MinidumpWriterTest, MappingInfo) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||
const char* kMemoryName = "a fake module";
|
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
char module_identifier_buffer[kGUIDStringSize];
|
||||
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||
module_identifier_buffer,
|
||||
sizeof(module_identifier_buffer));
|
||||
string module_identifier(module_identifier_buffer);
|
||||
// Strip out dashes
|
||||
size_t pos;
|
||||
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||
module_identifier.erase(pos, 1);
|
||||
}
|
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
|
||||
// Get some memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.tid = 1;
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
|
||||
// Add information about the mapped memory.
|
||||
MappingInfo info;
|
||||
info.start_addr = kMemoryAddress;
|
||||
info.size = kMemorySize;
|
||||
info.offset = 0;
|
||||
strcpy(info.name, kMemoryName);
|
||||
|
||||
MappingList mappings;
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings));
|
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module =
|
||||
module_list->GetModuleForAddress(kMemoryAddress);
|
||||
ASSERT_TRUE(module);
|
||||
|
||||
EXPECT_EQ(kMemoryAddress, module->base_address());
|
||||
EXPECT_EQ(kMemorySize, module->size());
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(templ);
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
// Test that mapping info can be specified, and that it overrides
|
||||
// existing mappings that are wholly contained within the specified
|
||||
// range.
|
||||
TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||
const char* kMemoryName = "a fake module";
|
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
char module_identifier_buffer[kGUIDStringSize];
|
||||
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||
module_identifier_buffer,
|
||||
sizeof(module_identifier_buffer));
|
||||
string module_identifier(module_identifier_buffer);
|
||||
// Strip out dashes
|
||||
size_t pos;
|
||||
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||
module_identifier.erase(pos, 1);
|
||||
}
|
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
|
||||
// mmap a file
|
||||
char tempfile[] = TEMPDIR "/minidump-writer-unittest-temp-XXXXXX";
|
||||
mktemp(tempfile);
|
||||
int fd = open(tempfile, O_RDWR | O_CREAT, 0);
|
||||
ASSERT_NE(-1, fd);
|
||||
unlink(tempfile);
|
||||
// fill with zeros
|
||||
char buffer[kMemorySize];
|
||||
memset(buffer, 0, kMemorySize);
|
||||
ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize));
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE,
|
||||
fd,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
close(fd);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.tid = 1;
|
||||
|
||||
char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(dumpfile);
|
||||
|
||||
// Add information about the mapped memory. Report it as being larger than
|
||||
// it actually is.
|
||||
MappingInfo info;
|
||||
info.start_addr = kMemoryAddress - kMemorySize;
|
||||
info.size = kMemorySize * 3;
|
||||
info.offset = 0;
|
||||
strcpy(info.name, kMemoryName);
|
||||
|
||||
MappingList mappings;
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(
|
||||
WriteMinidump(dumpfile, child, &context, sizeof(context), mappings));
|
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(dumpfile);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module =
|
||||
module_list->GetModuleForAddress(kMemoryAddress);
|
||||
ASSERT_TRUE(module);
|
||||
|
||||
EXPECT_EQ(info.start_addr, module->base_address());
|
||||
EXPECT_EQ(info.size, module->size());
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(dumpfile);
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
static const int kNumberOfThreadsInHelperProgram = 1;
|
||||
char kNumberOfThreadsArgument[2];
|
||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
||||
|
||||
// Locate helper binary next to the current binary.
|
||||
char self_path[PATH_MAX];
|
||||
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
|
||||
FAIL() << "readlink failed: " << strerror(errno);
|
||||
exit(1);
|
||||
}
|
||||
string helper_path(self_path);
|
||||
size_t pos = helper_path.rfind('/');
|
||||
if (pos == string::npos) {
|
||||
FAIL() << "no trailing slash in path: " << helper_path;
|
||||
exit(1);
|
||||
}
|
||||
helper_path.erase(pos + 1);
|
||||
helper_path += "linux_dumper_unittest_helper";
|
||||
|
||||
// Copy binary to a temp file.
|
||||
char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX";
|
||||
mktemp(binpath);
|
||||
char cmdline[2 * PATH_MAX];
|
||||
sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath);
|
||||
ASSERT_EQ(0, system(cmdline));
|
||||
ASSERT_EQ(0, chmod(binpath, 0755));
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
// In child process.
|
||||
close(fds[0]);
|
||||
|
||||
// Pass the pipe fd and the number of threads as arguments.
|
||||
char pipe_fd_string[8];
|
||||
sprintf(pipe_fd_string, "%d", fds[1]);
|
||||
execl(binpath,
|
||||
binpath,
|
||||
pipe_fd_string,
|
||||
kNumberOfThreadsArgument,
|
||||
NULL);
|
||||
}
|
||||
close(fds[1]);
|
||||
// Wait for the child process to signal that it's ready.
|
||||
struct pollfd pfd;
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = fds[0];
|
||||
pfd.events = POLLIN | POLLERR;
|
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
|
||||
ASSERT_EQ(1, r);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
uint8_t junk;
|
||||
read(fds[0], &junk, sizeof(junk));
|
||||
close(fds[0]);
|
||||
|
||||
// Child is ready now.
|
||||
// Unlink the test binary.
|
||||
unlink(binpath);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1;
|
||||
ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context)));
|
||||
kill(child_pid, SIGKILL);
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
|
||||
|
||||
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
// Check that the main module filename is correct.
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module = module_list->GetMainModule();
|
||||
EXPECT_STREQ(binpath, module->code_file().c_str());
|
||||
// Check that the file ID is correct.
|
||||
FileID fileid(helper_path.c_str());
|
||||
uint8_t identifier[sizeof(MDGUID)];
|
||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
|
||||
char identifier_string[kGUIDStringSize];
|
||||
FileID::ConvertIdentifierToString(identifier,
|
||||
identifier_string,
|
||||
kGUIDStringSize);
|
||||
string module_identifier(identifier_string);
|
||||
// Strip out dashes
|
||||
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||
module_identifier.erase(pos, 1);
|
||||
}
|
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(templ);
|
||||
}
|
||||
|
104
src/client/linux/sender/google_crash_report_sender.cc
Normal file
104
src/client/linux/sender/google_crash_report_sender.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2009, 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/google_crashdump_uploader.h"
|
||||
#include "third_party/linux/include/gflags/gflags.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using std::string;
|
||||
|
||||
DEFINE_string(crash_server, "http://clients2.google.com/cr",
|
||||
"The crash server to upload minidumps to.");
|
||||
DEFINE_string(product_name, "",
|
||||
"The product name that the minidump corresponds to.");
|
||||
DEFINE_string(product_version, "",
|
||||
"The version of the product that produced the minidump.");
|
||||
DEFINE_string(client_id, "",
|
||||
"The client GUID");
|
||||
DEFINE_string(minidump_path, "",
|
||||
"The path of the minidump file.");
|
||||
DEFINE_string(ptime, "",
|
||||
"The process uptime in milliseconds.");
|
||||
DEFINE_string(ctime, "",
|
||||
"The cumulative process uptime in milliseconds.");
|
||||
DEFINE_string(email, "",
|
||||
"The user's email address.");
|
||||
DEFINE_string(comments, "",
|
||||
"Extra user comments");
|
||||
DEFINE_string(proxy_host, "",
|
||||
"Proxy host");
|
||||
DEFINE_string(proxy_userpasswd, "",
|
||||
"Proxy username/password in user:pass format.");
|
||||
|
||||
|
||||
bool CheckForRequiredFlagsOrDie() {
|
||||
std::string error_text = "";
|
||||
if (FLAGS_product_name.empty()) {
|
||||
error_text.append("\nProduct name must be specified.");
|
||||
}
|
||||
|
||||
if (FLAGS_product_version.empty()) {
|
||||
error_text.append("\nProduct version must be specified.");
|
||||
}
|
||||
|
||||
if (FLAGS_client_id.empty()) {
|
||||
error_text.append("\nClient ID must be specified.");
|
||||
}
|
||||
|
||||
if (FLAGS_minidump_path.empty()) {
|
||||
error_text.append("\nMinidump pathname must be specified.");
|
||||
}
|
||||
|
||||
if (!error_text.empty()) {
|
||||
std::cout << error_text;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (!CheckForRequiredFlagsOrDie()) {
|
||||
return 1;
|
||||
}
|
||||
google_breakpad::GoogleCrashdumpUploader g(FLAGS_product_name,
|
||||
FLAGS_product_version,
|
||||
FLAGS_client_id,
|
||||
FLAGS_ptime,
|
||||
FLAGS_ctime,
|
||||
FLAGS_email,
|
||||
FLAGS_comments,
|
||||
FLAGS_minidump_path,
|
||||
FLAGS_crash_server,
|
||||
FLAGS_proxy_host,
|
||||
FLAGS_proxy_userpasswd);
|
||||
g.Upload();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -50,10 +50,13 @@ extern "C" {
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
// Keys for configuration file
|
||||
// Keys for configuration file
|
||||
#define kReporterMinidumpDirectoryKey "MinidumpDir"
|
||||
#define kReporterMinidumpIDKey "MinidumpID"
|
||||
|
||||
// Filename for recording uploaded IDs
|
||||
#define kReporterLogFilename "uploads.log"
|
||||
|
||||
// The default subdirectory of the Library to put crash dumps in
|
||||
// The subdirectory is
|
||||
// ~/Library/<kDefaultLibrarySubdirectory>/<GoogleBreakpadProduct>
|
||||
@@ -91,6 +94,10 @@ extern "C" {
|
||||
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
|
||||
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand"
|
||||
|
||||
// A service name associated with the original bootstrap parent port, saved in
|
||||
// OnDemandServer and restored in Inspector.
|
||||
#define BREAKPAD_BOOTSTRAP_PARENT_PORT "com.Breakpad.BootstrapParent"
|
||||
|
||||
// Optional user-defined function to dec to decide if we should handle
|
||||
// this crash or forward it along.
|
||||
// Return true if you want Breakpad to handle it.
|
||||
@@ -110,16 +117,19 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
|
||||
// Key: Value:
|
||||
// BREAKPAD_PRODUCT Product name (e.g., "MyAwesomeProduct")
|
||||
// This one is used as the key to identify
|
||||
// the product when uploading
|
||||
// the product when uploading. Falls back to
|
||||
// CFBundleName if not specified.
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_PRODUCT_DISPLAY This is the display name, e.g. a pretty
|
||||
// name for the product when the crash_sender
|
||||
// pops up UI for the user. Falls back to
|
||||
// pops up UI for the user. Falls back first to
|
||||
// CFBundleDisplayName and then to
|
||||
// BREAKPAD_PRODUCT if not specified.
|
||||
//
|
||||
// BREAKPAD_VERSION Product version (e.g., 1.2.3), used
|
||||
// as metadata for crash report
|
||||
// as metadata for crash report. Falls back to
|
||||
// CFBundleVersion if not specified.
|
||||
// REQUIRED
|
||||
//
|
||||
// BREAKPAD_VENDOR Vendor name, used in UI (e.g. "A report has
|
||||
|
@@ -55,6 +55,10 @@
|
||||
|
||||
|
||||
using google_breakpad::KeyValueEntry;
|
||||
using google_breakpad::MachPortSender;
|
||||
using google_breakpad::MachReceiveMessage;
|
||||
using google_breakpad::MachSendMessage;
|
||||
using google_breakpad::ReceivePort;
|
||||
using google_breakpad::SimpleStringDictionary;
|
||||
using google_breakpad::SimpleStringDictionaryIterator;
|
||||
|
||||
@@ -265,7 +269,7 @@ bool Breakpad::ExceptionHandlerDirectCallback(void *context,
|
||||
//=============================================================================
|
||||
#pragma mark -
|
||||
|
||||
#include <mach-o/dyld.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
//=============================================================================
|
||||
// Returns the pathname to the Resources directory for this version of
|
||||
@@ -286,28 +290,21 @@ NSString * GetResourcePath() {
|
||||
//
|
||||
|
||||
// Get the pathname to the code which contains this function
|
||||
void *address = nil;
|
||||
NSModule module = nil;
|
||||
_dyld_lookup_and_bind_fully("_GetResourcePath",
|
||||
&address,
|
||||
&module);
|
||||
|
||||
if (module && address) {
|
||||
const char* moduleName = NSNameOfModule(module);
|
||||
if (moduleName) {
|
||||
// The "Resources" directory should be in the same directory as the
|
||||
// executable code, since that's how the Breakpad framework is built.
|
||||
resourcePath = [NSString stringWithUTF8String:moduleName];
|
||||
resourcePath = [resourcePath stringByDeletingLastPathComponent];
|
||||
resourcePath = [resourcePath stringByAppendingPathComponent:@"Resources/"];
|
||||
} else {
|
||||
DEBUGLOG(stderr, "Missing moduleName\n");
|
||||
}
|
||||
Dl_info info;
|
||||
if (dladdr((const void*)GetResourcePath, &info) != 0) {
|
||||
NSFileManager *filemgr = [NSFileManager defaultManager];
|
||||
NSString *filePath =
|
||||
[filemgr stringWithFileSystemRepresentation:info.dli_fname
|
||||
length:strlen(info.dli_fname)];
|
||||
NSString *bundlePath = [filePath stringByDeletingLastPathComponent];
|
||||
// The "Resources" directory should be in the same directory as the
|
||||
// executable code, since that's how the Breakpad framework is built.
|
||||
resourcePath = [bundlePath stringByAppendingPathComponent:@"Resources/"];
|
||||
} else {
|
||||
DEBUGLOG(stderr, "Could not find GetResourcePath\n");
|
||||
// fallback plan
|
||||
NSBundle *bundle =
|
||||
[NSBundle bundleWithIdentifier:@"com.Google.BreakpadFramework"];
|
||||
[NSBundle bundleWithIdentifier:@"com.Google.BreakpadFramework"];
|
||||
resourcePath = [bundle resourcePath];
|
||||
}
|
||||
|
||||
@@ -371,9 +368,10 @@ bool Breakpad::Initialize(NSDictionary *parameters) {
|
||||
|
||||
// Create the handler (allocating it in our special protected pool)
|
||||
handler_ =
|
||||
new (gBreakpadAllocator->Allocate(sizeof(google_breakpad::ExceptionHandler)))
|
||||
google_breakpad::ExceptionHandler(
|
||||
Breakpad::ExceptionHandlerDirectCallback, this, true);
|
||||
new (gBreakpadAllocator->Allocate(
|
||||
sizeof(google_breakpad::ExceptionHandler)))
|
||||
google_breakpad::ExceptionHandler(
|
||||
Breakpad::ExceptionHandlerDirectCallback, this, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -403,22 +401,23 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
NSString *urlStr = [parameters objectForKey:@BREAKPAD_URL];
|
||||
NSString *interval = [parameters objectForKey:@BREAKPAD_REPORT_INTERVAL];
|
||||
NSString *inspectorPathString =
|
||||
[parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION];
|
||||
[parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION];
|
||||
NSString *reporterPathString =
|
||||
[parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION];
|
||||
[parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION];
|
||||
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
||||
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
||||
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
||||
NSString *logFileTailSize =
|
||||
[parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
||||
NSString *requestUserText =
|
||||
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
||||
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
||||
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
||||
NSString *vendor =
|
||||
[parameters objectForKey:@BREAKPAD_VENDOR];
|
||||
[parameters objectForKey:@BREAKPAD_VENDOR];
|
||||
NSString *dumpSubdirectory =
|
||||
[parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY];
|
||||
[parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY];
|
||||
|
||||
NSDictionary *serverParameters =
|
||||
[parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT];
|
||||
[parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT];
|
||||
|
||||
// These may have been set above as user prefs, which take priority.
|
||||
if (!skipConfirm) {
|
||||
@@ -431,8 +430,12 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
if (!product)
|
||||
product = [parameters objectForKey:@"CFBundleName"];
|
||||
|
||||
if (!display)
|
||||
display = product;
|
||||
if (!display) {
|
||||
display = [parameters objectForKey:@"CFBundleDisplayName"];
|
||||
if (!display) {
|
||||
display = product;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version)
|
||||
version = [parameters objectForKey:@"CFBundleVersion"];
|
||||
@@ -512,8 +515,10 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
// Find Reporter.
|
||||
if (!reporterPathString) {
|
||||
reporterPathString =
|
||||
[resourcePath stringByAppendingPathComponent:@"crash_report_sender.app"];
|
||||
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
|
||||
[resourcePath
|
||||
stringByAppendingPathComponent:@"crash_report_sender.app"];
|
||||
reporterPathString =
|
||||
[[NSBundle bundleWithPath:reporterPathString] executablePath];
|
||||
}
|
||||
|
||||
// Verify that there is a Reporter application.
|
||||
@@ -558,9 +563,9 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
dictionary.SetKeyValue(BREAKPAD_SKIP_CONFIRM, [skipConfirm UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_CONFIRM_TIMEOUT, [timeout UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_INSPECTOR_LOCATION,
|
||||
[inspectorPathString fileSystemRepresentation]);
|
||||
[inspectorPathString fileSystemRepresentation]);
|
||||
dictionary.SetKeyValue(BREAKPAD_REPORTER_EXE_LOCATION,
|
||||
[reporterPathString fileSystemRepresentation]);
|
||||
[reporterPathString fileSystemRepresentation]);
|
||||
dictionary.SetKeyValue(BREAKPAD_LOGFILE_UPLOAD_SIZE,
|
||||
[logFileTailSize UTF8String]);
|
||||
dictionary.SetKeyValue(BREAKPAD_REQUEST_COMMENTS,
|
||||
@@ -573,7 +578,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
char timeStartedString[32];
|
||||
sprintf(timeStartedString, "%d", tv.tv_sec);
|
||||
sprintf(timeStartedString, "%zd", tv.tv_sec);
|
||||
dictionary.SetKeyValue(BREAKPAD_PROCESS_START_TIME,
|
||||
timeStartedString);
|
||||
|
||||
@@ -591,7 +596,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
// For each key-value pair, call BreakpadAddUploadParameter()
|
||||
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
||||
NSString *aParameter;
|
||||
while (aParameter = [keyEnumerator nextObject]) {
|
||||
while ((aParameter = [keyEnumerator nextObject])) {
|
||||
BreakpadAddUploadParameter(this, aParameter,
|
||||
[serverParameters objectForKey:aParameter]);
|
||||
}
|
||||
@@ -600,7 +605,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::SetKeyValue(NSString *key, NSString *value) {
|
||||
void Breakpad::SetKeyValue(NSString *key, NSString *value) {
|
||||
// We allow nil values. This is the same as removing the keyvalue.
|
||||
if (!config_params_ || !key)
|
||||
return;
|
||||
@@ -609,7 +614,7 @@ void Breakpad::SetKeyValue(NSString *key, NSString *value) {
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
NSString * Breakpad::KeyValue(NSString *key) {
|
||||
NSString *Breakpad::KeyValue(NSString *key) {
|
||||
if (!config_params_ || !key)
|
||||
return nil;
|
||||
|
||||
@@ -618,25 +623,24 @@ NSString * Breakpad::KeyValue(NSString *key) {
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::RemoveKeyValue(NSString *key) {
|
||||
if (!config_params_ || !key)
|
||||
return;
|
||||
void Breakpad::RemoveKeyValue(NSString *key) {
|
||||
if (!config_params_ || !key) return;
|
||||
|
||||
config_params_->RemoveKey([key UTF8String]);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::GenerateAndSendReport() {
|
||||
void Breakpad::GenerateAndSendReport() {
|
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES");
|
||||
HandleException(0, 0, 0, mach_thread_self());
|
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO");
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
bool Breakpad::HandleException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread) {
|
||||
bool Breakpad::HandleException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread) {
|
||||
DEBUGLOG(stderr, "Breakpad: an exception occurred\n");
|
||||
|
||||
if (filter_callback_) {
|
||||
@@ -712,8 +716,7 @@ bool Breakpad::HandleException(int exception_type,
|
||||
|
||||
// If we don't want any forwarding, return true here to indicate that we've
|
||||
// processed things as much as we want.
|
||||
if (send_and_exit_)
|
||||
return true;
|
||||
if (send_and_exit_) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -739,73 +742,67 @@ BreakpadRef BreakpadCreate(NSDictionary *parameters) {
|
||||
// since once it does its allocations and locks the memory, smashes to itself
|
||||
// don't affect anything we care about.
|
||||
gMasterAllocator =
|
||||
new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2);
|
||||
new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2);
|
||||
|
||||
gKeyValueAllocator =
|
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
||||
ProtectedMemoryAllocator(sizeof(SimpleStringDictionary));
|
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
||||
ProtectedMemoryAllocator(sizeof(SimpleStringDictionary));
|
||||
|
||||
// Create a mutex for use in accessing the SimpleStringDictionary
|
||||
int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
|
||||
if (mutexResult != 0) {
|
||||
throw mutexResult; // caught down below
|
||||
}
|
||||
if (mutexResult == 0) {
|
||||
|
||||
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes.
|
||||
// Let's round up to the nearest page size.
|
||||
//
|
||||
int breakpad_pool_size = 4096;
|
||||
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes.
|
||||
// Let's round up to the nearest page size.
|
||||
//
|
||||
int breakpad_pool_size = 4096;
|
||||
|
||||
/*
|
||||
sizeof(Breakpad)
|
||||
+ sizeof(google_breakpad::ExceptionHandler)
|
||||
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler )
|
||||
*/
|
||||
/*
|
||||
sizeof(Breakpad)
|
||||
+ sizeof(google_breakpad::ExceptionHandler)
|
||||
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler )
|
||||
*/
|
||||
|
||||
gBreakpadAllocator =
|
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
||||
ProtectedMemoryAllocator(breakpad_pool_size);
|
||||
gBreakpadAllocator =
|
||||
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
||||
ProtectedMemoryAllocator(breakpad_pool_size);
|
||||
|
||||
// Stack-based autorelease pool for Breakpad::Create() obj-c code.
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
Breakpad *breakpad = Breakpad::Create(parameters);
|
||||
// Stack-based autorelease pool for Breakpad::Create() obj-c code.
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
Breakpad *breakpad = Breakpad::Create(parameters);
|
||||
|
||||
if (breakpad) {
|
||||
// Make read-only to protect against memory smashers
|
||||
gMasterAllocator->Protect();
|
||||
gKeyValueAllocator->Protect();
|
||||
gBreakpadAllocator->Protect();
|
||||
// Can uncomment this line to figure out how much space was actually
|
||||
// allocated using this allocator
|
||||
// printf("gBreakpadAllocator allocated size = %d\n",
|
||||
// gBreakpadAllocator->GetAllocatedSize() );
|
||||
[pool release];
|
||||
return (BreakpadRef)breakpad;
|
||||
}
|
||||
|
||||
if (breakpad) {
|
||||
// Make read-only to protect against memory smashers
|
||||
gMasterAllocator->Protect();
|
||||
gKeyValueAllocator->Protect();
|
||||
gBreakpadAllocator->Protect();
|
||||
} else {
|
||||
[pool release];
|
||||
#ifdef __EXCEPTIONS
|
||||
throw(-1);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Can uncomment this line to figure out how much space was actually
|
||||
// allocated using this allocator
|
||||
// printf("gBreakpadAllocator allocated size = %d\n",
|
||||
// gBreakpadAllocator->GetAllocatedSize() );
|
||||
|
||||
[pool release];
|
||||
return (BreakpadRef)breakpad;
|
||||
} catch(...) { // don't let exceptions leave this C API
|
||||
if (gKeyValueAllocator) {
|
||||
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
||||
gKeyValueAllocator = NULL;
|
||||
}
|
||||
|
||||
if (gBreakpadAllocator) {
|
||||
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
||||
gBreakpadAllocator = NULL;
|
||||
}
|
||||
|
||||
delete gMasterAllocator;
|
||||
gMasterAllocator = NULL;
|
||||
fprintf(stderr, "BreakpadCreate() : error\n");
|
||||
}
|
||||
|
||||
if (gKeyValueAllocator) {
|
||||
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
||||
gKeyValueAllocator = NULL;
|
||||
}
|
||||
|
||||
if (gBreakpadAllocator) {
|
||||
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
||||
gBreakpadAllocator = NULL;
|
||||
}
|
||||
|
||||
delete gMasterAllocator;
|
||||
gMasterAllocator = NULL;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@@ -28,12 +28,16 @@
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import "OnDemandServer.h"
|
||||
#import "Breakpad.h"
|
||||
|
||||
#if DEBUG
|
||||
#define PRINT_MACH_RESULT(result_, message_) \
|
||||
printf(message_"%s (%d)\n", mach_error_string(result_), result_ );
|
||||
#define PRINT_BOOTSTRAP_RESULT(result_, message_) \
|
||||
printf(message_"%s (%d)\n", bootstrap_strerror(result_), result_ );
|
||||
#else
|
||||
#define PRINT_MACH_RESULT(result_, message_)
|
||||
#define PRINT_BOOTSTRAP_RESULT(result_, message_)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
@@ -67,15 +71,44 @@ kern_return_t OnDemandServer::Initialize(const char *server_command,
|
||||
bool unregister_on_cleanup) {
|
||||
unregister_on_cleanup_ = unregister_on_cleanup;
|
||||
|
||||
kern_return_t kr =
|
||||
bootstrap_create_server(bootstrap_port,
|
||||
const_cast<char*>(server_command),
|
||||
geteuid(), // server uid
|
||||
true,
|
||||
&server_port_);
|
||||
mach_port_t self_task = mach_task_self();
|
||||
|
||||
mach_port_t bootstrap_port;
|
||||
kern_return_t kr = task_get_bootstrap_port(self_task, &bootstrap_port);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "bootstrap_create_server() : ");
|
||||
PRINT_MACH_RESULT(kr, "task_get_bootstrap_port(): ");
|
||||
return kr;
|
||||
}
|
||||
|
||||
mach_port_t bootstrap_subset_port;
|
||||
kr = bootstrap_subset(bootstrap_port, self_task, &bootstrap_subset_port);
|
||||
if (kr != BOOTSTRAP_SUCCESS) {
|
||||
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_subset(): ");
|
||||
return kr;
|
||||
}
|
||||
|
||||
// The inspector will be invoked with its bootstrap port set to the subset,
|
||||
// but the sender will need access to the original bootstrap port. Although
|
||||
// the original port is the subset's parent, bootstrap_parent can't be used
|
||||
// because it requires extra privileges. Stash the original bootstrap port
|
||||
// in the subset by registering it under a known name. The inspector will
|
||||
// recover this port and set it as its own bootstrap port in Inspector.mm
|
||||
// Inspector::ResetBootstrapPort.
|
||||
kr = bootstrap_register(bootstrap_subset_port,
|
||||
const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
|
||||
bootstrap_port);
|
||||
if (kr != BOOTSTRAP_SUCCESS) {
|
||||
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_register(): ");
|
||||
return kr;
|
||||
}
|
||||
|
||||
kr = bootstrap_create_server(bootstrap_subset_port,
|
||||
const_cast<char*>(server_command),
|
||||
geteuid(), // server uid
|
||||
true,
|
||||
&server_port_);
|
||||
if (kr != BOOTSTRAP_SUCCESS) {
|
||||
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_create_server(): ");
|
||||
return kr;
|
||||
}
|
||||
|
||||
@@ -86,15 +119,14 @@ kern_return_t OnDemandServer::Initialize(const char *server_command,
|
||||
kr = bootstrap_create_service(server_port_,
|
||||
const_cast<char*>(service_name),
|
||||
&service_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "bootstrap_create_service() : ");
|
||||
if (kr != BOOTSTRAP_SUCCESS) {
|
||||
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_create_service(): ");
|
||||
|
||||
// perhaps the service has already been created - try to look it up
|
||||
kr = bootstrap_look_up(bootstrap_port, (char*)service_name, &service_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "bootstrap_look_up() : ");
|
||||
if (kr != BOOTSTRAP_SUCCESS) {
|
||||
PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_look_up(): ");
|
||||
Unregister(); // clean up server port
|
||||
return kr;
|
||||
}
|
||||
@@ -116,7 +148,7 @@ void OnDemandServer::LaunchOnDemand() {
|
||||
// and holding on to this port delays launching until the current process
|
||||
// exits!
|
||||
mach_port_deallocate(mach_task_self(), server_port_);
|
||||
server_port_ = NULL;
|
||||
server_port_ = MACH_PORT_DEAD;
|
||||
|
||||
// Now, the service is still registered and all we need to do is send
|
||||
// a mach message to the service port in order to launch the server.
|
||||
|
@@ -33,6 +33,8 @@
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
#import "client/mac/handler/minidump_generator.h"
|
||||
|
||||
#define VERBOSE 0
|
||||
@@ -78,7 +80,7 @@ using google_breakpad::MinidumpGenerator;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath);
|
||||
BOOL EnsureDirectoryPathExists(NSString *dirPath);
|
||||
|
||||
//=============================================================================
|
||||
class ConfigFile {
|
||||
@@ -123,7 +125,7 @@ class ConfigFile {
|
||||
//=============================================================================
|
||||
class MinidumpLocation {
|
||||
public:
|
||||
MinidumpLocation(const NSString *minidumpDir) {
|
||||
MinidumpLocation(NSString *minidumpDir) {
|
||||
// Ensure that the path exists. Fallback to /tmp if unable to locate path.
|
||||
assert(minidumpDir);
|
||||
if (!EnsureDirectoryPathExists(minidumpDir)) {
|
||||
@@ -163,6 +165,18 @@ class Inspector {
|
||||
void Inspect(const char *receive_port_name);
|
||||
|
||||
private:
|
||||
// The Inspector is invoked with its bootstrap port set to the bootstrap
|
||||
// subset established in OnDemandServer.mm OnDemandServer::Initialize.
|
||||
// For proper communication with the system, the sender (which will inherit
|
||||
// the Inspector's bootstrap port) needs the per-session bootstrap namespace
|
||||
// available directly in its bootstrap port. OnDemandServer stashed this
|
||||
// port into the subset namespace under a special name. ResetBootstrapPort
|
||||
// recovers this port and switches this task to use it as its own bootstrap
|
||||
// (ensuring that children like the sender will inherit it), and saves the
|
||||
// subset in bootstrap_subset_port_ for use by ServiceCheckIn and
|
||||
// ServiceCheckOut.
|
||||
kern_return_t ResetBootstrapPort();
|
||||
|
||||
kern_return_t ServiceCheckIn(const char *receive_port_name);
|
||||
kern_return_t ServiceCheckOut(const char *receive_port_name);
|
||||
|
||||
@@ -174,6 +188,10 @@ class Inspector {
|
||||
|
||||
void SetCrashTimeParameters();
|
||||
|
||||
// The bootstrap port in which the inspector is registered and into which it
|
||||
// must check in.
|
||||
mach_port_t bootstrap_subset_port_;
|
||||
|
||||
mach_port_t service_rcv_port_;
|
||||
|
||||
int exception_type_;
|
||||
|
@@ -44,6 +44,8 @@
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
#import "common/mac/MachIPC.h"
|
||||
|
||||
#import "GTMDefines.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if VERBOSE
|
||||
@@ -55,7 +57,7 @@
|
||||
namespace google_breakpad {
|
||||
|
||||
//=============================================================================
|
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath) {
|
||||
BOOL EnsureDirectoryPathExists(NSString *dirPath) {
|
||||
NSFileManager *mgr = [NSFileManager defaultManager];
|
||||
|
||||
// If we got a relative path, prepend the current directory
|
||||
@@ -91,14 +93,14 @@ static BOOL EnsureDirectoryPathExists(NSString *dirPath) {
|
||||
// Break up the difference into components
|
||||
NSString *diff = [dirPath substringFromIndex:[common length] + 1];
|
||||
NSArray *components = [diff pathComponents];
|
||||
unsigned count = [components count];
|
||||
NSUInteger count = [components count];
|
||||
|
||||
// Rebuild the path one component at a time
|
||||
NSDictionary *attrs =
|
||||
[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750]
|
||||
forKey:NSFilePosixPermissions];
|
||||
path = common;
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
for (NSUInteger i = 0; i < count; ++i) {
|
||||
path = [path stringByAppendingPathComponent:[components objectAtIndex:i]];
|
||||
|
||||
if (![mgr createDirectoryAtPath:path attributes:attrs])
|
||||
@@ -201,7 +203,12 @@ void ConfigFile::WriteFile(const SimpleStringDictionary *configurationParameters
|
||||
|
||||
//=============================================================================
|
||||
void Inspector::Inspect(const char *receive_port_name) {
|
||||
kern_return_t result = ServiceCheckIn(receive_port_name);
|
||||
kern_return_t result = ResetBootstrapPort();
|
||||
if (result != KERN_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
result = ServiceCheckIn(receive_port_name);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
result = ReadMessages();
|
||||
@@ -238,11 +245,50 @@ void Inspector::Inspect(const char *receive_port_name) {
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ResetBootstrapPort() {
|
||||
// A reasonable default, in case anything fails.
|
||||
bootstrap_subset_port_ = bootstrap_port;
|
||||
|
||||
mach_port_t self_task = mach_task_self();
|
||||
|
||||
kern_return_t kr = task_get_bootstrap_port(self_task,
|
||||
&bootstrap_subset_port_);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)",
|
||||
mach_error_string(kr), kr);
|
||||
return kr;
|
||||
}
|
||||
|
||||
mach_port_t bootstrap_parent_port;
|
||||
kr = bootstrap_look_up(bootstrap_subset_port_,
|
||||
const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
|
||||
&bootstrap_parent_port);
|
||||
if (kr != BOOTSTRAP_SUCCESS) {
|
||||
NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)",
|
||||
bootstrap_strerror(kr), kr);
|
||||
return kr;
|
||||
}
|
||||
|
||||
kr = task_set_bootstrap_port(self_task, bootstrap_parent_port);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)",
|
||||
mach_error_string(kr), kr);
|
||||
return kr;
|
||||
}
|
||||
|
||||
// Some things access the bootstrap port through this global variable
|
||||
// instead of calling task_get_bootstrap_port.
|
||||
bootstrap_port = bootstrap_parent_port;
|
||||
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) {
|
||||
// We need to get the mach port representing this service, so we can
|
||||
// get information from the crashed process.
|
||||
kern_return_t kr = bootstrap_check_in(bootstrap_port,
|
||||
kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_,
|
||||
(char*)receive_port_name,
|
||||
&service_rcv_port_);
|
||||
|
||||
@@ -273,7 +319,7 @@ kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) {
|
||||
}
|
||||
|
||||
// Unregister the service associated with the receive port.
|
||||
kr = bootstrap_register(bootstrap_port,
|
||||
kr = bootstrap_register(bootstrap_subset_port_,
|
||||
(char*)receive_port_name,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
@@ -329,12 +375,12 @@ kern_return_t Inspector::ReadMessages() {
|
||||
// we are expected to read.
|
||||
// Read each key/value pair, one mach message per key/value pair.
|
||||
for (unsigned int i = 0; i < info.parameter_count; ++i) {
|
||||
MachReceiveMessage message;
|
||||
result = receive_port.WaitForMessage(&message, 1000);
|
||||
MachReceiveMessage parameter_message;
|
||||
result = receive_port.WaitForMessage(¶meter_message, 1000);
|
||||
|
||||
if(result == KERN_SUCCESS) {
|
||||
KeyValueMessageData &key_value_data =
|
||||
(KeyValueMessageData&)*message.GetData();
|
||||
(KeyValueMessageData&)*parameter_message.GetData();
|
||||
// If we get a blank key, make sure we don't increment the
|
||||
// parameter count; in some cases (notably on-demand generation
|
||||
// many times in a short period of time) caused the Mach IPC
|
||||
@@ -376,11 +422,11 @@ void Inspector::SetCrashTimeParameters() {
|
||||
if (processStartTimeString) {
|
||||
time_t processStartTime = strtol(processStartTimeString, NULL, 10);
|
||||
time_t processUptime = tv.tv_sec - processStartTime;
|
||||
sprintf(processUptimeString, "%d", processUptime);
|
||||
sprintf(processUptimeString, "%zd", processUptime);
|
||||
config_params_.SetKeyValue(BREAKPAD_PROCESS_UP_TIME, processUptimeString);
|
||||
}
|
||||
|
||||
sprintf(processCrashtimeString, "%d", tv.tv_sec);
|
||||
sprintf(processCrashtimeString, "%zd", tv.tv_sec);
|
||||
config_params_.SetKeyValue(BREAKPAD_PROCESS_CRASH_TIME,
|
||||
processCrashtimeString);
|
||||
}
|
||||
|
47
src/client/mac/crash_generation/client_info.h
Normal file
47
src/client/mac/crash_generation/client_info.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2010 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 CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_
|
||||
#define CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class ClientInfo {
|
||||
public:
|
||||
explicit ClientInfo(pid_t pid) : pid_(pid) {}
|
||||
|
||||
pid_t pid() const { return pid_; }
|
||||
|
||||
private:
|
||||
pid_t pid_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_
|
72
src/client/mac/crash_generation/crash_generation_client.cc
Normal file
72
src/client/mac/crash_generation/crash_generation_client.cc
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2010 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 "client/mac/crash_generation/crash_generation_client.h"
|
||||
|
||||
#include "client/mac/crash_generation/crash_generation_server.h"
|
||||
#include "common/mac/MachIPC.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
bool CrashGenerationClient::RequestDumpForException(
|
||||
int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread) {
|
||||
// The server will send a message to this port indicating that it
|
||||
// has finished its work.
|
||||
ReceivePort acknowledge_port;
|
||||
|
||||
MachSendMessage message(kDumpRequestMessage);
|
||||
message.AddDescriptor(mach_task_self()); // this task
|
||||
message.AddDescriptor(crashing_thread); // crashing thread
|
||||
message.AddDescriptor(mach_thread_self()); // handler thread
|
||||
message.AddDescriptor(acknowledge_port.GetPort()); // message receive port
|
||||
|
||||
ExceptionInfo info;
|
||||
info.exception_type = exception_type;
|
||||
info.exception_code = exception_code;
|
||||
info.exception_subcode = exception_subcode;
|
||||
message.SetData(&info, sizeof(info));
|
||||
|
||||
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000;
|
||||
kern_return_t result = sender_.SendMessage(message, kSendTimeoutMs);
|
||||
if (result != KERN_SUCCESS)
|
||||
return false;
|
||||
|
||||
// Give the server slightly longer to reply since it has to
|
||||
// inspect this task and write the minidump.
|
||||
const mach_msg_timeout_t kReceiveTimeoutMs = 5 * 1000;
|
||||
MachReceiveMessage acknowledge_message;
|
||||
result = acknowledge_port.WaitForMessage(&acknowledge_message,
|
||||
kReceiveTimeoutMs);
|
||||
return result == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
65
src/client/mac/crash_generation/crash_generation_client.h
Normal file
65
src/client/mac/crash_generation/crash_generation_client.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2010 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_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
||||
#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
||||
|
||||
#include "common/mac/MachIPC.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class CrashGenerationClient {
|
||||
public:
|
||||
explicit CrashGenerationClient(const char* mach_port_name)
|
||||
: sender_(mach_port_name) {
|
||||
}
|
||||
|
||||
// Request the crash server to generate a dump.
|
||||
//
|
||||
// Return true if the dump was successful; false otherwise.
|
||||
bool RequestDumpForException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t crashing_thread);
|
||||
|
||||
bool RequestDump() {
|
||||
return RequestDumpForException(0, 0, 0, MACH_PORT_NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
MachPortSender sender_;
|
||||
|
||||
// Prevent copy construction and assignment.
|
||||
CrashGenerationClient(const CrashGenerationClient&);
|
||||
CrashGenerationClient& operator=(const CrashGenerationClient&);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_
|
160
src/client/mac/crash_generation/crash_generation_server.cc
Normal file
160
src/client/mac/crash_generation/crash_generation_server.cc
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) 2010 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 "client/mac/crash_generation/crash_generation_server.h"
|
||||
|
||||
#include "client/mac/crash_generation/client_info.h"
|
||||
#include "client/mac/handler/minidump_generator.h"
|
||||
#include "common/mac/scoped_task_suspend-inl.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
CrashGenerationServer::CrashGenerationServer(
|
||||
const char *mach_port_name,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void *dump_context,
|
||||
OnClientExitingCallback exit_callback,
|
||||
void *exit_context,
|
||||
bool generate_dumps,
|
||||
const std::string &dump_path)
|
||||
: dump_callback_(dump_callback),
|
||||
dump_context_(dump_context),
|
||||
exit_callback_(exit_callback),
|
||||
exit_context_(exit_context),
|
||||
generate_dumps_(generate_dumps),
|
||||
dump_dir_(dump_path.empty() ? "/tmp" : dump_path),
|
||||
started_(false),
|
||||
receive_port_(mach_port_name),
|
||||
mach_port_name_(mach_port_name) {
|
||||
}
|
||||
|
||||
CrashGenerationServer::~CrashGenerationServer() {
|
||||
if (started_)
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::Start() {
|
||||
int thread_create_result = pthread_create(&server_thread_, NULL,
|
||||
&WaitForMessages, this);
|
||||
started_ = thread_create_result == 0;
|
||||
return started_;
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::Stop() {
|
||||
if (!started_)
|
||||
return false;
|
||||
|
||||
// Send a quit message to the background thread, and then join it.
|
||||
MachPortSender sender(mach_port_name_.c_str());
|
||||
MachSendMessage quit_message(kQuitMessage);
|
||||
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000;
|
||||
kern_return_t result = sender.SendMessage(quit_message, kSendTimeoutMs);
|
||||
if (result == KERN_SUCCESS) {
|
||||
int thread_join_result = pthread_join(server_thread_, NULL);
|
||||
started_ = thread_join_result != 0;
|
||||
}
|
||||
|
||||
return !started_;
|
||||
}
|
||||
|
||||
// static
|
||||
void *CrashGenerationServer::WaitForMessages(void *server) {
|
||||
CrashGenerationServer *self =
|
||||
reinterpret_cast<CrashGenerationServer*>(server);
|
||||
while (self->WaitForOneMessage()) {}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool CrashGenerationServer::WaitForOneMessage() {
|
||||
MachReceiveMessage message;
|
||||
kern_return_t result = receive_port_.WaitForMessage(&message,
|
||||
MACH_MSG_TIMEOUT_NONE);
|
||||
if (result == KERN_SUCCESS) {
|
||||
switch (message.GetMessageID()) {
|
||||
case kDumpRequestMessage: {
|
||||
ExceptionInfo &info = (ExceptionInfo &)*message.GetData();
|
||||
|
||||
mach_port_t remote_task = message.GetTranslatedPort(0);
|
||||
mach_port_t crashing_thread = message.GetTranslatedPort(1);
|
||||
mach_port_t handler_thread = message.GetTranslatedPort(2);
|
||||
mach_port_t ack_port = message.GetTranslatedPort(3);
|
||||
pid_t remote_pid = -1;
|
||||
pid_for_task(remote_task, &remote_pid);
|
||||
ClientInfo client(remote_pid);
|
||||
|
||||
bool result;
|
||||
std::string dump_path;
|
||||
if (generate_dumps_) {
|
||||
ScopedTaskSuspend suspend(remote_task);
|
||||
|
||||
MinidumpGenerator generator(remote_task, handler_thread);
|
||||
dump_path = generator.UniqueNameInDirectory(dump_dir_, NULL);
|
||||
|
||||
if (info.exception_type && info.exception_code) {
|
||||
generator.SetExceptionInformation(info.exception_type,
|
||||
info.exception_code,
|
||||
info.exception_subcode,
|
||||
crashing_thread);
|
||||
}
|
||||
result = generator.Write(dump_path.c_str());
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (result && dump_callback_) {
|
||||
dump_callback_(dump_context_, client, dump_path);
|
||||
}
|
||||
|
||||
// TODO(ted): support a way for the client to send additional data,
|
||||
// perhaps with a callback so users of the server can read the data
|
||||
// themselves?
|
||||
|
||||
if (ack_port != MACH_PORT_DEAD && ack_port != MACH_PORT_NULL) {
|
||||
MachPortSender sender(ack_port);
|
||||
MachSendMessage ack_message(kAcknowledgementMessage);
|
||||
const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000;
|
||||
|
||||
sender.SendMessage(ack_message, kSendTimeoutMs);
|
||||
}
|
||||
|
||||
if (exit_callback_) {
|
||||
exit_callback_(exit_context_, client);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kQuitMessage:
|
||||
return false;
|
||||
}
|
||||
} else { // result != KERN_SUCCESS
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
141
src/client/mac/crash_generation/crash_generation_server.h
Normal file
141
src/client/mac/crash_generation/crash_generation_server.h
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2010 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_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
||||
#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/mac/MachIPC.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class ClientInfo;
|
||||
|
||||
// Messages the server can read via its mach port
|
||||
enum {
|
||||
kDumpRequestMessage = 1,
|
||||
kAcknowledgementMessage = 2,
|
||||
kQuitMessage = 3
|
||||
};
|
||||
|
||||
// Exception details sent by the client when requesting a dump.
|
||||
struct ExceptionInfo {
|
||||
int32_t exception_type;
|
||||
int32_t exception_code;
|
||||
int32_t exception_subcode;
|
||||
};
|
||||
|
||||
class CrashGenerationServer {
|
||||
public:
|
||||
// WARNING: callbacks may be invoked on a different thread
|
||||
// than that which creates the CrashGenerationServer. They must
|
||||
// be thread safe.
|
||||
typedef void (*OnClientDumpRequestCallback)(void *context,
|
||||
const ClientInfo &client_info,
|
||||
const std::string &file_path);
|
||||
|
||||
typedef void (*OnClientExitingCallback)(void *context,
|
||||
const ClientInfo &client_info);
|
||||
|
||||
// Create an instance with the given parameters.
|
||||
//
|
||||
// mach_port_name: Named server port to listen on.
|
||||
// dump_callback: Callback for a client crash dump request.
|
||||
// dump_context: Context for client crash dump request callback.
|
||||
// exit_callback: Callback for client process exit.
|
||||
// exit_context: Context for client exit callback.
|
||||
// generate_dumps: Whether to automatically generate dumps.
|
||||
// Client code of this class might want to generate dumps explicitly
|
||||
// in the crash dump request callback. In that case, false can be
|
||||
// passed for this parameter.
|
||||
// dump_path: Path for generating dumps; required only if true is
|
||||
// passed for generateDumps parameter; NULL can be passed otherwise.
|
||||
CrashGenerationServer(const char *mach_port_name,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void *dump_context,
|
||||
OnClientExitingCallback exit_callback,
|
||||
void *exit_context,
|
||||
bool generate_dumps,
|
||||
const std::string &dump_path);
|
||||
|
||||
~CrashGenerationServer();
|
||||
|
||||
// Perform initialization steps needed to start listening to clients.
|
||||
//
|
||||
// Return true if initialization is successful; false otherwise.
|
||||
bool Start();
|
||||
|
||||
// Stop the server.
|
||||
bool Stop();
|
||||
|
||||
private:
|
||||
// Return a unique filename at which a minidump can be written.
|
||||
bool MakeMinidumpFilename(std::string &outFilename);
|
||||
|
||||
// Loop reading client messages and responding to them until
|
||||
// a quit message is received.
|
||||
static void *WaitForMessages(void *server);
|
||||
|
||||
// Wait for a single client message and respond to it. Returns false
|
||||
// if a quit message was received or if an error occurred.
|
||||
bool WaitForOneMessage();
|
||||
|
||||
OnClientDumpRequestCallback dump_callback_;
|
||||
void *dump_context_;
|
||||
|
||||
OnClientExitingCallback exit_callback_;
|
||||
void *exit_context_;
|
||||
|
||||
bool generate_dumps_;
|
||||
|
||||
std::string dump_dir_;
|
||||
|
||||
bool started_;
|
||||
|
||||
// The mach port that receives requests to dump from child processes.
|
||||
ReceivePort receive_port_;
|
||||
|
||||
// The name of the mach port. Stored so the Stop method can message
|
||||
// the background thread to shut it down.
|
||||
std::string mach_port_name_;
|
||||
|
||||
// The thread that waits on the receive port.
|
||||
pthread_t server_thread_;
|
||||
|
||||
// Disable copy constructor and operator=.
|
||||
CrashGenerationServer(const CrashGenerationServer&);
|
||||
CrashGenerationServer& operator=(const CrashGenerationServer&);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_
|
@@ -54,7 +54,7 @@
|
||||
*/
|
||||
|
||||
|
||||
/* nealsid:
|
||||
/*
|
||||
* This file was copied from libc/gen/nlist.c from Darwin's source code
|
||||
* The version of nlist used as a base is from 10.5.2, libc-498
|
||||
* http://www.opensource.apple.com/darwinsource/10.5.2/Libc-498/gen/nlist.c
|
||||
@@ -62,24 +62,22 @@
|
||||
* The full tarball is at:
|
||||
* http://www.opensource.apple.com/darwinsource/tarballs/apsl/Libc-498.tar.gz
|
||||
*
|
||||
* I've modified it to be compatible with 64-bit images. However,
|
||||
* 32-bit compatibility has not been retained.
|
||||
* I've modified it to be compatible with 64-bit images.
|
||||
*/
|
||||
|
||||
#ifdef __LP64__
|
||||
#include "breakpad_nlist_64.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <mach/mach.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
#include "breakpad_nlist_64.h"
|
||||
#include <TargetConditionals.h>
|
||||
#include <stdio.h>
|
||||
#include <mach/mach.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Stuff lifted from <a.out.h> and <sys/exec.h> since they are gone */
|
||||
/*
|
||||
@@ -108,44 +106,77 @@ struct exec {
|
||||
#define N_SYMOFF(x) \
|
||||
(N_TXTOFF(x) + (x).a_text+(x).a_data + (x).a_trsize+(x).a_drsize)
|
||||
|
||||
// Traits structs for specializing function templates to handle
|
||||
// 32-bit/64-bit Mach-O files.
|
||||
template<typename T>
|
||||
struct MachBits {};
|
||||
|
||||
typedef struct nlist nlist32;
|
||||
typedef struct nlist_64 nlist64;
|
||||
|
||||
template<>
|
||||
struct MachBits<nlist32> {
|
||||
typedef mach_header mach_header_type;
|
||||
typedef uint32_t word_type;
|
||||
static const uint32_t magic = MH_MAGIC;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct MachBits<nlist64> {
|
||||
typedef mach_header_64 mach_header_type;
|
||||
typedef uint64_t word_type;
|
||||
static const uint32_t magic = MH_MAGIC_64;
|
||||
};
|
||||
|
||||
template<typename nlist_type>
|
||||
int
|
||||
__breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames);
|
||||
__breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames,
|
||||
cpu_type_t cpu_type);
|
||||
|
||||
/*
|
||||
* nlist - retreive attributes from name list (string table version)
|
||||
*/
|
||||
|
||||
int
|
||||
breakpad_nlist_64(const char *name,
|
||||
breakpad_nlist *list,
|
||||
const char **symbolNames) {
|
||||
int fd, n;
|
||||
|
||||
fd = open(name, O_RDONLY, 0);
|
||||
template <typename nlist_type>
|
||||
int breakpad_nlist_common(const char *name,
|
||||
nlist_type *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
int fd = open(name, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return (-1);
|
||||
n = __breakpad_fdnlist_64(fd, list, symbolNames);
|
||||
(void)close(fd);
|
||||
return (n);
|
||||
return -1;
|
||||
int n = __breakpad_fdnlist(fd, list, symbolNames, cpu_type);
|
||||
close(fd);
|
||||
return n;
|
||||
}
|
||||
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
return breakpad_nlist_common(name, list, symbolNames, cpu_type);
|
||||
}
|
||||
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist_64 *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
return breakpad_nlist_common(name, list, symbolNames, cpu_type);
|
||||
}
|
||||
|
||||
/* Note: __fdnlist() is called from kvm_nlist in libkvm's kvm.c */
|
||||
|
||||
int
|
||||
__breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames) {
|
||||
register breakpad_nlist *p, *q;
|
||||
breakpad_nlist space[BUFSIZ/sizeof (breakpad_nlist)];
|
||||
template<typename nlist_type>
|
||||
int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames,
|
||||
cpu_type_t cpu_type) {
|
||||
typedef typename MachBits<nlist_type>::mach_header_type mach_header_type;
|
||||
typedef typename MachBits<nlist_type>::word_type word_type;
|
||||
|
||||
const register char *s1, *s2;
|
||||
register int n, m;
|
||||
int maxlen, nreq;
|
||||
off_t sa; /* symbol address */
|
||||
off_t ss; /* start of strings */
|
||||
struct exec buf;
|
||||
unsigned arch_offset = 0;
|
||||
const uint32_t magic = MachBits<nlist_type>::magic;
|
||||
|
||||
maxlen = 500;
|
||||
for (q = list, nreq = 0;
|
||||
int maxlen = 500;
|
||||
int nreq = 0;
|
||||
for (nlist_type* q = list;
|
||||
symbolNames[q-list] && symbolNames[q-list][0];
|
||||
q++, nreq++) {
|
||||
|
||||
@@ -156,226 +187,226 @@ __breakpad_fdnlist_64(int fd, breakpad_nlist *list, const char **symbolNames) {
|
||||
q->n_un.n_strx = 0;
|
||||
}
|
||||
|
||||
struct exec buf;
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf) ||
|
||||
(N_BADMAG(buf) && *((long *)&buf) != MH_MAGIC &&
|
||||
(N_BADMAG(buf) && *((uint32_t *)&buf) != magic &&
|
||||
NXSwapBigLongToHost(*((long *)&buf)) != FAT_MAGIC) &&
|
||||
/* nealsid: The following is the big-endian ppc64 check */
|
||||
/* The following is the big-endian ppc64 check */
|
||||
(*((uint32_t*)&buf)) != FAT_MAGIC) {
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Deal with fat file if necessary */
|
||||
unsigned arch_offset = 0;
|
||||
if (NXSwapBigLongToHost(*((long *)&buf)) == FAT_MAGIC ||
|
||||
/* nealsid: The following is the big-endian ppc64 check */
|
||||
*((int*)&buf) == FAT_MAGIC) {
|
||||
/* The following is the big-endian ppc64 check */
|
||||
*((unsigned int *)&buf) == FAT_MAGIC) {
|
||||
/* Get host info */
|
||||
host_t host = mach_host_self();
|
||||
unsigned i = HOST_BASIC_INFO_COUNT;
|
||||
struct host_basic_info hbi;
|
||||
struct fat_header fh;
|
||||
struct fat_arch *fat_archs, *fap;
|
||||
unsigned i;
|
||||
host_t host;
|
||||
|
||||
/* Get our host info */
|
||||
host = mach_host_self();
|
||||
i = HOST_BASIC_INFO_COUNT;
|
||||
kern_return_t kr;
|
||||
if ((kr=host_info(host, HOST_BASIC_INFO,
|
||||
(host_info_t)(&hbi), &i)) != KERN_SUCCESS) {
|
||||
return (-1);
|
||||
if ((kr = host_info(host, HOST_BASIC_INFO,
|
||||
(host_info_t)(&hbi), &i)) != KERN_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
mach_port_deallocate(mach_task_self(), host);
|
||||
|
||||
/* Read in the fat header */
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
struct fat_header fh;
|
||||
if (lseek(fd, 0, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)&fh, sizeof(fh)) != sizeof(fh)) {
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Convert fat_narchs to host byte order */
|
||||
fh.nfat_arch = NXSwapBigLongToHost(fh.nfat_arch);
|
||||
fh.nfat_arch = NXSwapBigIntToHost(fh.nfat_arch);
|
||||
|
||||
/* Read in the fat archs */
|
||||
fat_archs = (struct fat_arch *)malloc(fh.nfat_arch *
|
||||
sizeof(struct fat_arch));
|
||||
struct fat_arch *fat_archs =
|
||||
(struct fat_arch *)malloc(fh.nfat_arch * sizeof(struct fat_arch));
|
||||
if (fat_archs == NULL) {
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)fat_archs,
|
||||
sizeof(struct fat_arch) * fh.nfat_arch) !=
|
||||
sizeof(struct fat_arch) * fh.nfat_arch) {
|
||||
(ssize_t)(sizeof(struct fat_arch) * fh.nfat_arch)) {
|
||||
free(fat_archs);
|
||||
return (-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert archs to host byte ordering (a constraint of
|
||||
* cpusubtype_getbestarch()
|
||||
*/
|
||||
for (i = 0; i < fh.nfat_arch; i++) {
|
||||
for (unsigned i = 0; i < fh.nfat_arch; i++) {
|
||||
fat_archs[i].cputype =
|
||||
NXSwapBigLongToHost(fat_archs[i].cputype);
|
||||
NXSwapBigIntToHost(fat_archs[i].cputype);
|
||||
fat_archs[i].cpusubtype =
|
||||
NXSwapBigLongToHost(fat_archs[i].cpusubtype);
|
||||
NXSwapBigIntToHost(fat_archs[i].cpusubtype);
|
||||
fat_archs[i].offset =
|
||||
NXSwapBigLongToHost(fat_archs[i].offset);
|
||||
NXSwapBigIntToHost(fat_archs[i].offset);
|
||||
fat_archs[i].size =
|
||||
NXSwapBigLongToHost(fat_archs[i].size);
|
||||
NXSwapBigIntToHost(fat_archs[i].size);
|
||||
fat_archs[i].align =
|
||||
NXSwapBigLongToHost(fat_archs[i].align);
|
||||
NXSwapBigIntToHost(fat_archs[i].align);
|
||||
}
|
||||
|
||||
fap = NULL;
|
||||
for (i = 0; i < fh.nfat_arch; i++) {
|
||||
/* nealsid: Although the original Apple code uses host_info */
|
||||
/* to retrieve the CPU type, the host_info will still return */
|
||||
/* CPU_TYPE_X86 even if running as an x86_64 binary. Given that */
|
||||
/* this code isn't necessary on i386, I've decided to hardcode */
|
||||
/* looking for a 64-bit binary */
|
||||
#if TARGET_CPU_X86_64
|
||||
if (fat_archs[i].cputype == CPU_TYPE_X86_64) {
|
||||
#elif TARGET_CPU_PPC64
|
||||
if (fat_archs[i].cputype == CPU_TYPE_POWERPC64) {
|
||||
#else
|
||||
#error undefined cpu!
|
||||
{
|
||||
#endif
|
||||
fap = &fat_archs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fap) {
|
||||
free(fat_archs);
|
||||
return (-1);
|
||||
}
|
||||
arch_offset = fap->offset;
|
||||
free(fat_archs);
|
||||
|
||||
/* Read in the beginning of the architecture-specific file */
|
||||
lseek(fd, arch_offset, SEEK_SET);
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf)) {
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (*((int *)&buf) == MH_MAGIC_64) {
|
||||
struct mach_header_64 mh;
|
||||
struct load_command *load_commands, *lcp;
|
||||
struct symtab_command *stp;
|
||||
long i;
|
||||
|
||||
lseek(fd, arch_offset, SEEK_SET);
|
||||
if (read(fd, (char *)&mh, sizeof(mh)) != sizeof(mh)) {
|
||||
return (-1);
|
||||
}
|
||||
load_commands = (struct load_command *)malloc(mh.sizeofcmds);
|
||||
if (load_commands == NULL) {
|
||||
return (-1);
|
||||
}
|
||||
if (read(fd, (char *)load_commands, mh.sizeofcmds) !=
|
||||
mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
stp = NULL;
|
||||
lcp = load_commands;
|
||||
// nealsid:iterate through all load commands, looking for
|
||||
// LC_SYMTAB load command
|
||||
for (i = 0; i < mh.ncmds; i++) {
|
||||
if (lcp->cmdsize % sizeof(long) != 0 ||
|
||||
lcp->cmdsize <= 0 ||
|
||||
(char *)lcp + lcp->cmdsize >
|
||||
(char *)load_commands + mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
if (lcp->cmd == LC_SYMTAB) {
|
||||
if (lcp->cmdsize !=
|
||||
sizeof(struct symtab_command)) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
stp = (struct symtab_command *)lcp;
|
||||
break;
|
||||
}
|
||||
lcp = (struct load_command *)
|
||||
((char *)lcp + lcp->cmdsize);
|
||||
}
|
||||
if (stp == NULL) {
|
||||
free(load_commands);
|
||||
return (-1);
|
||||
}
|
||||
// sa points to the beginning of the symbol table
|
||||
sa = stp->symoff + arch_offset;
|
||||
// ss points to the beginning of the string table
|
||||
ss = stp->stroff + arch_offset;
|
||||
// n is the number of bytes in the symbol table
|
||||
// each symbol table entry is an nlist structure
|
||||
n = stp->nsyms * sizeof(breakpad_nlist);
|
||||
free(load_commands);
|
||||
}
|
||||
else {
|
||||
sa = N_SYMOFF(buf) + arch_offset;
|
||||
ss = sa + buf.a_syms + arch_offset;
|
||||
n = buf.a_syms;
|
||||
}
|
||||
|
||||
lseek(fd, sa, SEEK_SET);
|
||||
|
||||
// the algorithm here is to read the nlist entries in m-sized
|
||||
// chunks into q. q is then iterated over. for each entry in q,
|
||||
// use the string table index(q->n_un.n_strx) to read the symbol
|
||||
// name, then scan the nlist entries passed in by the user(via p),
|
||||
// and look for a match
|
||||
while (n) {
|
||||
long savpos;
|
||||
|
||||
m = sizeof (space);
|
||||
if (n < m)
|
||||
m = n;
|
||||
if (read(fd, (char *)space, m) != m)
|
||||
struct fat_arch *fap = NULL;
|
||||
for (unsigned i = 0; i < fh.nfat_arch; i++) {
|
||||
if (fat_archs[i].cputype == cpu_type) {
|
||||
fap = &fat_archs[i];
|
||||
break;
|
||||
n -= m;
|
||||
savpos = lseek(fd, 0, SEEK_CUR);
|
||||
for (q = space; (m -= sizeof(breakpad_nlist)) >= 0; q++) {
|
||||
char nambuf[BUFSIZ];
|
||||
|
||||
if (q->n_un.n_strx == 0 || q->n_type & N_STAB)
|
||||
continue;
|
||||
|
||||
// seek to the location in the binary where the symbol
|
||||
// name is stored & read it into memory
|
||||
lseek(fd, ss+q->n_un.n_strx, SEEK_SET);
|
||||
read(fd, nambuf, maxlen+1);
|
||||
s2 = nambuf;
|
||||
for (p = list;
|
||||
symbolNames[p-list] &&
|
||||
symbolNames[p-list][0];
|
||||
p++) {
|
||||
// get the symbol name the user has passed in that
|
||||
// corresponds to the nlist entry that we're looking at
|
||||
s1 = symbolNames[p - list];
|
||||
while (*s1) {
|
||||
if (*s1++ != *s2++)
|
||||
goto cont;
|
||||
}
|
||||
if (*s2)
|
||||
goto cont;
|
||||
|
||||
p->n_value = q->n_value;
|
||||
p->n_type = q->n_type;
|
||||
p->n_desc = q->n_desc;
|
||||
p->n_sect = q->n_sect;
|
||||
p->n_un.n_strx = q->n_un.n_strx;
|
||||
if (--nreq == 0)
|
||||
return (nreq);
|
||||
|
||||
break;
|
||||
cont: ;
|
||||
}
|
||||
}
|
||||
lseek(fd, savpos, SEEK_SET);
|
||||
}
|
||||
return (nreq);
|
||||
|
||||
if (!fap) {
|
||||
free(fat_archs);
|
||||
return -1;
|
||||
}
|
||||
arch_offset = fap->offset;
|
||||
free(fat_archs);
|
||||
|
||||
/* Read in the beginning of the architecture-specific file */
|
||||
if (lseek(fd, arch_offset, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __LP64__ */
|
||||
off_t sa; /* symbol address */
|
||||
off_t ss; /* start of strings */
|
||||
register register_t n;
|
||||
if (*((unsigned int *)&buf) == magic) {
|
||||
if (lseek(fd, arch_offset, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
mach_header_type mh;
|
||||
if (read(fd, (char *)&mh, sizeof(mh)) != sizeof(mh)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct load_command *load_commands =
|
||||
(struct load_command *)malloc(mh.sizeofcmds);
|
||||
if (load_commands == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, (char *)load_commands, mh.sizeofcmds) !=
|
||||
(ssize_t)mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
struct symtab_command *stp = NULL;
|
||||
struct load_command *lcp = load_commands;
|
||||
// iterate through all load commands, looking for
|
||||
// LC_SYMTAB load command
|
||||
for (uint32_t i = 0; i < mh.ncmds; i++) {
|
||||
if (lcp->cmdsize % sizeof(word_type) != 0 ||
|
||||
lcp->cmdsize <= 0 ||
|
||||
(char *)lcp + lcp->cmdsize >
|
||||
(char *)load_commands + mh.sizeofcmds) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
if (lcp->cmd == LC_SYMTAB) {
|
||||
if (lcp->cmdsize !=
|
||||
sizeof(struct symtab_command)) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
stp = (struct symtab_command *)lcp;
|
||||
break;
|
||||
}
|
||||
lcp = (struct load_command *)
|
||||
((char *)lcp + lcp->cmdsize);
|
||||
}
|
||||
if (stp == NULL) {
|
||||
free(load_commands);
|
||||
return -1;
|
||||
}
|
||||
// sa points to the beginning of the symbol table
|
||||
sa = stp->symoff + arch_offset;
|
||||
// ss points to the beginning of the string table
|
||||
ss = stp->stroff + arch_offset;
|
||||
// n is the number of bytes in the symbol table
|
||||
// each symbol table entry is an nlist structure
|
||||
n = stp->nsyms * sizeof(nlist_type);
|
||||
free(load_commands);
|
||||
} else {
|
||||
sa = N_SYMOFF(buf) + arch_offset;
|
||||
ss = sa + buf.a_syms + arch_offset;
|
||||
n = buf.a_syms;
|
||||
}
|
||||
|
||||
if (lseek(fd, sa, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// the algorithm here is to read the nlist entries in m-sized
|
||||
// chunks into q. q is then iterated over. for each entry in q,
|
||||
// use the string table index(q->n_un.n_strx) to read the symbol
|
||||
// name, then scan the nlist entries passed in by the user(via p),
|
||||
// and look for a match
|
||||
while (n) {
|
||||
nlist_type space[BUFSIZ/sizeof (nlist_type)];
|
||||
register register_t m = sizeof (space);
|
||||
|
||||
if (n < m)
|
||||
m = n;
|
||||
if (read(fd, (char *)space, m) != m)
|
||||
break;
|
||||
n -= m;
|
||||
long savpos = lseek(fd, 0, SEEK_CUR);
|
||||
if (savpos == -1) {
|
||||
return -1;
|
||||
}
|
||||
for (nlist_type* q = space; (m -= sizeof(nlist_type)) >= 0; q++) {
|
||||
char nambuf[BUFSIZ];
|
||||
|
||||
if (q->n_un.n_strx == 0 || q->n_type & N_STAB)
|
||||
continue;
|
||||
|
||||
// seek to the location in the binary where the symbol
|
||||
// name is stored & read it into memory
|
||||
if (lseek(fd, ss+q->n_un.n_strx, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (read(fd, nambuf, maxlen+1) == -1) {
|
||||
return -1;
|
||||
}
|
||||
const char *s2 = nambuf;
|
||||
for (nlist_type *p = list;
|
||||
symbolNames[p-list] && symbolNames[p-list][0];
|
||||
p++) {
|
||||
// get the symbol name the user has passed in that
|
||||
// corresponds to the nlist entry that we're looking at
|
||||
const char *s1 = symbolNames[p - list];
|
||||
while (*s1) {
|
||||
if (*s1++ != *s2++)
|
||||
goto cont;
|
||||
}
|
||||
if (*s2)
|
||||
goto cont;
|
||||
|
||||
p->n_value = q->n_value;
|
||||
p->n_type = q->n_type;
|
||||
p->n_desc = q->n_desc;
|
||||
p->n_sect = q->n_sect;
|
||||
p->n_un.n_strx = q->n_un.n_strx;
|
||||
if (--nreq == 0)
|
||||
return nreq;
|
||||
|
||||
break;
|
||||
cont: ;
|
||||
}
|
||||
}
|
||||
if (lseek(fd, savpos, SEEK_SET) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return nreq;
|
||||
}
|
||||
|
@@ -33,11 +33,15 @@
|
||||
|
||||
#ifndef CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__
|
||||
|
||||
typedef struct nlist_64 breakpad_nlist;
|
||||
#include <mach/machine.h>
|
||||
|
||||
int
|
||||
breakpad_nlist_64(const char *name,
|
||||
breakpad_nlist *list,
|
||||
const char **symbolNames);
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type);
|
||||
int breakpad_nlist(const char *name,
|
||||
struct nlist_64 *list,
|
||||
const char **symbolNames,
|
||||
cpu_type_t cpu_type);
|
||||
|
||||
#endif /* CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ */
|
||||
|
@@ -27,6 +27,8 @@
|
||||
// (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 "client/mac/handler/dynamic_images.h"
|
||||
|
||||
extern "C" { // needed to compile on Leopard
|
||||
#include <mach-o/nlist.h>
|
||||
#include <stdlib.h>
|
||||
@@ -34,13 +36,42 @@ extern "C" { // needed to compile on Leopard
|
||||
}
|
||||
|
||||
#include "breakpad_nlist_64.h"
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <assert.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <dlfcn.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <mach/task_info.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include "client/mac/handler/dynamic_images.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
|
||||
|
||||
// Fallback declarations for TASK_DYLD_INFO and friends, introduced in
|
||||
// <mach/task_info.h> in the Mac OS X 10.6 SDK.
|
||||
#define TASK_DYLD_INFO 17
|
||||
struct task_dyld_info {
|
||||
mach_vm_address_t all_image_info_addr;
|
||||
mach_vm_size_t all_image_info_size;
|
||||
};
|
||||
typedef struct task_dyld_info task_dyld_info_data_t;
|
||||
typedef struct task_dyld_info *task_dyld_info_t;
|
||||
#define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t))
|
||||
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
//==============================================================================
|
||||
// Returns the size of the memory region containing |address| and the
|
||||
// number of bytes from |address| to the end of the region.
|
||||
@@ -50,7 +81,7 @@ namespace google_breakpad {
|
||||
// straddle two vm regions.
|
||||
//
|
||||
static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
||||
const void* address,
|
||||
const uint64_t address,
|
||||
mach_vm_size_t *size_to_end) {
|
||||
mach_vm_address_t region_base = (mach_vm_address_t)address;
|
||||
mach_vm_size_t region_size;
|
||||
@@ -115,8 +146,8 @@ static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
|
||||
//
|
||||
// Warning! This will not read any strings longer than kMaxStringLength-1
|
||||
//
|
||||
static void* ReadTaskString(task_port_t target_task,
|
||||
const void* address) {
|
||||
static string ReadTaskString(task_port_t target_task,
|
||||
const uint64_t address) {
|
||||
// The problem is we don't know how much to read until we know how long
|
||||
// the string is. And we don't know how long the string is, until we've read
|
||||
// the memory! So, we'll try to read kMaxStringLength bytes
|
||||
@@ -128,68 +159,140 @@ static void* ReadTaskString(task_port_t target_task,
|
||||
mach_vm_size_t size_to_read =
|
||||
size_to_end > kMaxStringLength ? kMaxStringLength : size_to_end;
|
||||
|
||||
kern_return_t kr;
|
||||
return ReadTaskMemory(target_task, address, size_to_read, &kr);
|
||||
vector<uint8_t> bytes;
|
||||
if (ReadTaskMemory(target_task, address, (size_t)size_to_read, bytes) !=
|
||||
KERN_SUCCESS)
|
||||
return string();
|
||||
|
||||
return string(reinterpret_cast<const char*>(&bytes[0]));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return string();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Reads an address range from another task. A block of memory is malloced
|
||||
// and should be freed by the caller.
|
||||
void* ReadTaskMemory(task_port_t target_task,
|
||||
const void* address,
|
||||
size_t length,
|
||||
kern_return_t *kr) {
|
||||
void* result = NULL;
|
||||
// Reads an address range from another task. The bytes read will be returned
|
||||
// in bytes, which will be resized as necessary.
|
||||
kern_return_t ReadTaskMemory(task_port_t target_task,
|
||||
const uint64_t address,
|
||||
size_t length,
|
||||
vector<uint8_t> &bytes) {
|
||||
int systemPageSize = getpagesize();
|
||||
|
||||
// use the negative of the page size for the mask to find the page address
|
||||
mach_vm_address_t page_address =
|
||||
reinterpret_cast<mach_vm_address_t>(address) & (-systemPageSize);
|
||||
mach_vm_address_t page_address = address & (-systemPageSize);
|
||||
|
||||
mach_vm_address_t last_page_address =
|
||||
(reinterpret_cast<mach_vm_address_t>(address) + length +
|
||||
(systemPageSize - 1)) & (-systemPageSize);
|
||||
(address + length + (systemPageSize - 1)) & (-systemPageSize);
|
||||
|
||||
mach_vm_size_t page_size = last_page_address - page_address;
|
||||
uint8_t* local_start;
|
||||
uint32_t local_length;
|
||||
|
||||
kern_return_t r;
|
||||
kern_return_t r = mach_vm_read(target_task,
|
||||
page_address,
|
||||
page_size,
|
||||
reinterpret_cast<vm_offset_t*>(&local_start),
|
||||
&local_length);
|
||||
|
||||
r = mach_vm_read(target_task,
|
||||
page_address,
|
||||
page_size,
|
||||
reinterpret_cast<vm_offset_t*>(&local_start),
|
||||
&local_length);
|
||||
if (r != KERN_SUCCESS)
|
||||
return r;
|
||||
|
||||
|
||||
if (kr != NULL) {
|
||||
*kr = r;
|
||||
}
|
||||
|
||||
if (r == KERN_SUCCESS) {
|
||||
result = malloc(length);
|
||||
if (result != NULL) {
|
||||
memcpy(result,
|
||||
&local_start[(mach_vm_address_t)address - page_address],
|
||||
length);
|
||||
}
|
||||
mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length);
|
||||
}
|
||||
|
||||
return result;
|
||||
bytes.resize(length);
|
||||
memcpy(&bytes[0],
|
||||
&local_start[(mach_vm_address_t)address - page_address],
|
||||
length);
|
||||
mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length);
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
//==============================================================================
|
||||
// Traits structs for specializing function templates to handle
|
||||
// 32-bit/64-bit Mach-O files.
|
||||
struct MachO32 {
|
||||
typedef mach_header mach_header_type;
|
||||
typedef segment_command mach_segment_command_type;
|
||||
typedef dyld_image_info32 dyld_image_info;
|
||||
typedef dyld_all_image_infos32 dyld_all_image_infos;
|
||||
typedef struct nlist nlist_type;
|
||||
static const uint32_t magic = MH_MAGIC;
|
||||
static const uint32_t segment_load_command = LC_SEGMENT;
|
||||
};
|
||||
|
||||
struct MachO64 {
|
||||
typedef mach_header_64 mach_header_type;
|
||||
typedef segment_command_64 mach_segment_command_type;
|
||||
typedef dyld_image_info64 dyld_image_info;
|
||||
typedef dyld_all_image_infos64 dyld_all_image_infos;
|
||||
typedef struct nlist_64 nlist_type;
|
||||
static const uint32_t magic = MH_MAGIC_64;
|
||||
static const uint32_t segment_load_command = LC_SEGMENT_64;
|
||||
};
|
||||
|
||||
template<typename MachBits>
|
||||
bool FindTextSection(DynamicImage& image) {
|
||||
typedef typename MachBits::mach_header_type mach_header_type;
|
||||
typedef typename MachBits::mach_segment_command_type
|
||||
mach_segment_command_type;
|
||||
|
||||
const mach_header_type* header =
|
||||
reinterpret_cast<const mach_header_type*>(&image.header_[0]);
|
||||
|
||||
if(header->magic != MachBits::magic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
bool found_text_section = false;
|
||||
bool found_dylib_id_command = false;
|
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) {
|
||||
if (!found_text_section) {
|
||||
if (cmd->cmd == MachBits::segment_load_command) {
|
||||
const mach_segment_command_type *seg =
|
||||
reinterpret_cast<const mach_segment_command_type *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
image.vmaddr_ = seg->vmaddr;
|
||||
image.vmsize_ = seg->vmsize;
|
||||
image.slide_ = 0;
|
||||
|
||||
if (seg->fileoff == 0 && seg->filesize != 0) {
|
||||
image.slide_ =
|
||||
(uintptr_t)image.GetLoadAddress() - (uintptr_t)seg->vmaddr;
|
||||
}
|
||||
found_text_section = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_dylib_id_command) {
|
||||
if (cmd->cmd == LC_ID_DYLIB) {
|
||||
const struct dylib_command *dc =
|
||||
reinterpret_cast<const struct dylib_command *>(cmd);
|
||||
|
||||
image.version_ = dc->dylib.current_version;
|
||||
found_dylib_id_command = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_dylib_id_command && found_text_section) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<const struct load_command *>
|
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void DynamicImage::CalculateMemoryAndVersionInfo() {
|
||||
breakpad_mach_header *header = GetMachHeader();
|
||||
|
||||
// unless we can process the header, ensure that calls to
|
||||
// IsValid() will return false
|
||||
vmaddr_ = 0;
|
||||
@@ -197,78 +300,29 @@ void DynamicImage::CalculateMemoryAndVersionInfo() {
|
||||
slide_ = 0;
|
||||
version_ = 0;
|
||||
|
||||
bool foundTextSection = false;
|
||||
bool foundDylibIDCommand = false;
|
||||
|
||||
#if __LP64__
|
||||
if(header->magic != MH_MAGIC_64) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if(header->magic != MH_MAGIC) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __LP64__
|
||||
const uint32_t segmentLoadCommand = LC_SEGMENT_64;
|
||||
#else
|
||||
const uint32_t segmentLoadCommand = LC_SEGMENT;
|
||||
#endif
|
||||
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) {
|
||||
if (!foundTextSection) {
|
||||
if (cmd->cmd == segmentLoadCommand) {
|
||||
const breakpad_mach_segment_command *seg =
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
vmaddr_ = seg->vmaddr;
|
||||
vmsize_ = seg->vmsize;
|
||||
slide_ = 0;
|
||||
|
||||
if (seg->fileoff == 0 && seg->filesize != 0) {
|
||||
slide_ = (uintptr_t)GetLoadAddress() - (uintptr_t)seg->vmaddr;
|
||||
}
|
||||
foundTextSection = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundDylibIDCommand) {
|
||||
if (cmd->cmd == LC_ID_DYLIB) {
|
||||
const struct dylib_command *dc =
|
||||
reinterpret_cast<const struct dylib_command *>(cmd);
|
||||
|
||||
version_ = dc->dylib.current_version;
|
||||
foundDylibIDCommand = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDylibIDCommand && foundTextSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<const struct load_command *>
|
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize);
|
||||
}
|
||||
|
||||
// The function template above does all the real work.
|
||||
if (Is64Bit())
|
||||
FindTextSection<MachO64>(*this);
|
||||
else
|
||||
FindTextSection<MachO32>(*this);
|
||||
}
|
||||
|
||||
void DynamicImage::Print() {
|
||||
const char *path = GetFilePath();
|
||||
if (!path) {
|
||||
path = "(unknown)";
|
||||
}
|
||||
printf("%p: %s\n", GetLoadAddress(), path);
|
||||
breakpad_mach_header *header = GetMachHeader();
|
||||
MachHeader(*header).Print();
|
||||
printf("vmaddr\t\t: %p\n", reinterpret_cast<void*>(GetVMAddr()));
|
||||
printf("vmsize\t\t: %llu\n", GetVMSize());
|
||||
printf("slide\t\t: %td\n", GetVMAddrSlide());
|
||||
//==============================================================================
|
||||
// The helper function template abstracts the 32/64-bit differences.
|
||||
template<typename MachBits>
|
||||
uint32_t GetFileTypeFromHeader(DynamicImage& image) {
|
||||
typedef typename MachBits::mach_header_type mach_header_type;
|
||||
|
||||
const mach_header_type* header =
|
||||
reinterpret_cast<const mach_header_type*>(&image.header_[0]);
|
||||
return header->filetype;
|
||||
}
|
||||
|
||||
uint32_t DynamicImage::GetFileType() {
|
||||
if (Is64Bit())
|
||||
return GetFileTypeFromHeader<MachO64>(*this);
|
||||
|
||||
return GetFileTypeFromHeader<MachO32>(*this);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -276,146 +330,188 @@ void DynamicImage::Print() {
|
||||
//==============================================================================
|
||||
// Loads information about dynamically loaded code in the given task.
|
||||
DynamicImages::DynamicImages(mach_port_t task)
|
||||
: task_(task) {
|
||||
: task_(task),
|
||||
cpu_type_(DetermineTaskCPUType(task)),
|
||||
image_list_() {
|
||||
ReadImageInfoForTask();
|
||||
}
|
||||
|
||||
void* DynamicImages::GetDyldAllImageInfosPointer()
|
||||
{
|
||||
template<typename MachBits>
|
||||
static uint64_t LookupSymbol(const char* symbol_name,
|
||||
const char* filename,
|
||||
cpu_type_t cpu_type) {
|
||||
typedef typename MachBits::nlist_type nlist_type;
|
||||
|
||||
const char *imageSymbolName = "_dyld_all_image_infos";
|
||||
const char *dyldPath = "/usr/lib/dyld";
|
||||
#ifndef __LP64__
|
||||
struct nlist l[8];
|
||||
memset(l, 0, sizeof(l) );
|
||||
|
||||
// First we lookup the address of the "_dyld_all_image_infos" struct
|
||||
// which lives in "dyld". This structure contains information about all
|
||||
// of the loaded dynamic images.
|
||||
struct nlist &list = l[0];
|
||||
list.n_un.n_name = const_cast<char *>(imageSymbolName);
|
||||
nlist(dyldPath,&list);
|
||||
if(list.n_value) {
|
||||
return reinterpret_cast<void*>(list.n_value);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
#else
|
||||
struct nlist_64 l[8];
|
||||
struct nlist_64 &list = l[0];
|
||||
|
||||
memset(l, 0, sizeof(l) );
|
||||
|
||||
const char *symbolNames[2] = { imageSymbolName, "\0" };
|
||||
|
||||
int invalidEntriesCount = breakpad_nlist_64(dyldPath,&list,symbolNames);
|
||||
nlist_type symbol_info[8] = {};
|
||||
const char *symbolNames[2] = { symbol_name, "\0" };
|
||||
nlist_type &list = symbol_info[0];
|
||||
int invalidEntriesCount = breakpad_nlist(filename,
|
||||
&list,
|
||||
symbolNames,
|
||||
cpu_type);
|
||||
|
||||
if(invalidEntriesCount != 0) {
|
||||
return NULL;
|
||||
return 0;
|
||||
}
|
||||
assert(list.n_value);
|
||||
return reinterpret_cast<void*>(list.n_value);
|
||||
#endif
|
||||
|
||||
assert(list.n_value);
|
||||
return list.n_value;
|
||||
}
|
||||
|
||||
static SInt32 GetOSVersionInternal() {
|
||||
SInt32 os_version = 0;
|
||||
Gestalt(gestaltSystemVersion, &os_version);
|
||||
return os_version;
|
||||
}
|
||||
|
||||
static SInt32 GetOSVersion() {
|
||||
static SInt32 os_version = GetOSVersionInternal();
|
||||
return os_version;
|
||||
}
|
||||
|
||||
static bool IsSnowLeopardOrLater() {
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6
|
||||
return true;
|
||||
#else
|
||||
return GetOSVersion() >= 0x1060;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t DynamicImages::GetDyldAllImageInfosPointer() {
|
||||
if (IsSnowLeopardOrLater()) {
|
||||
task_dyld_info_data_t task_dyld_info;
|
||||
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
|
||||
if (task_info(task_, TASK_DYLD_INFO, (task_info_t)&task_dyld_info,
|
||||
&count) != KERN_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (uint64_t)task_dyld_info.all_image_info_addr;
|
||||
} else {
|
||||
const char *imageSymbolName = "_dyld_all_image_infos";
|
||||
const char *dyldPath = "/usr/lib/dyld";
|
||||
|
||||
if (Is64Bit())
|
||||
return LookupSymbol<MachO64>(imageSymbolName, dyldPath, cpu_type_);
|
||||
return LookupSymbol<MachO32>(imageSymbolName, dyldPath, cpu_type_);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// This code was written using dyld_debug.c (from Darwin) as a guide.
|
||||
void DynamicImages::ReadImageInfoForTask() {
|
||||
void *imageList = GetDyldAllImageInfosPointer();
|
||||
|
||||
if (imageList) {
|
||||
kern_return_t kr;
|
||||
// Read the structure inside of dyld that contains information about
|
||||
// loaded images. We're reading from the desired task's address space.
|
||||
template<typename MachBits>
|
||||
void ReadImageInfo(DynamicImages& images,
|
||||
uint64_t image_list_address) {
|
||||
typedef typename MachBits::dyld_image_info dyld_image_info;
|
||||
typedef typename MachBits::dyld_all_image_infos dyld_all_image_infos;
|
||||
typedef typename MachBits::mach_header_type mach_header_type;
|
||||
|
||||
// Here we make the assumption that dyld loaded at the same address in
|
||||
// the crashed process vs. this one. This is an assumption made in
|
||||
// "dyld_debug.c" and is said to be nearly always valid.
|
||||
dyld_all_image_infos *dyldInfo = reinterpret_cast<dyld_all_image_infos*>
|
||||
(ReadTaskMemory(task_,
|
||||
reinterpret_cast<void*>(imageList),
|
||||
sizeof(dyld_all_image_infos), &kr));
|
||||
// Read the structure inside of dyld that contains information about
|
||||
// loaded images. We're reading from the desired task's address space.
|
||||
|
||||
if (dyldInfo) {
|
||||
// number of loaded images
|
||||
int count = dyldInfo->infoArrayCount;
|
||||
// Here we make the assumption that dyld loaded at the same address in
|
||||
// the crashed process vs. this one. This is an assumption made in
|
||||
// "dyld_debug.c" and is said to be nearly always valid.
|
||||
vector<uint8_t> dyld_all_info_bytes;
|
||||
if (ReadTaskMemory(images.task_,
|
||||
image_list_address,
|
||||
sizeof(dyld_all_image_infos),
|
||||
dyld_all_info_bytes) != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
// Read an array of dyld_image_info structures each containing
|
||||
// information about a loaded image.
|
||||
dyld_image_info *infoArray = reinterpret_cast<dyld_image_info*>
|
||||
(ReadTaskMemory(task_,
|
||||
dyldInfo->infoArray,
|
||||
count*sizeof(dyld_image_info), &kr));
|
||||
dyld_all_image_infos *dyldInfo =
|
||||
reinterpret_cast<dyld_all_image_infos*>(&dyld_all_info_bytes[0]);
|
||||
|
||||
image_list_.reserve(count);
|
||||
// number of loaded images
|
||||
int count = dyldInfo->infoArrayCount;
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
dyld_image_info &info = infoArray[i];
|
||||
// Read an array of dyld_image_info structures each containing
|
||||
// information about a loaded image.
|
||||
vector<uint8_t> dyld_info_array_bytes;
|
||||
if (ReadTaskMemory(images.task_,
|
||||
dyldInfo->infoArray,
|
||||
count * sizeof(dyld_image_info),
|
||||
dyld_info_array_bytes) != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
// First read just the mach_header from the image in the task.
|
||||
breakpad_mach_header *header = reinterpret_cast<breakpad_mach_header*>
|
||||
(ReadTaskMemory(task_,
|
||||
info.load_address_,
|
||||
sizeof(breakpad_mach_header), &kr));
|
||||
dyld_image_info *infoArray =
|
||||
reinterpret_cast<dyld_image_info*>(&dyld_info_array_bytes[0]);
|
||||
images.image_list_.reserve(count);
|
||||
|
||||
if (!header)
|
||||
break; // bail on this dynamic image
|
||||
for (int i = 0; i < count; ++i) {
|
||||
dyld_image_info &info = infoArray[i];
|
||||
|
||||
// Now determine the total amount we really want to read based on the
|
||||
// size of the load commands. We need the header plus all of the
|
||||
// load commands.
|
||||
unsigned int header_size =
|
||||
sizeof(breakpad_mach_header) + header->sizeofcmds;
|
||||
// First read just the mach_header from the image in the task.
|
||||
vector<uint8_t> mach_header_bytes;
|
||||
if (ReadTaskMemory(images.task_,
|
||||
info.load_address_,
|
||||
sizeof(mach_header_type),
|
||||
mach_header_bytes) != KERN_SUCCESS)
|
||||
continue; // bail on this dynamic image
|
||||
|
||||
free(header);
|
||||
mach_header_type *header =
|
||||
reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]);
|
||||
|
||||
header = reinterpret_cast<breakpad_mach_header*>
|
||||
(ReadTaskMemory(task_, info.load_address_, header_size, &kr));
|
||||
// Now determine the total amount necessary to read the header
|
||||
// plus all of the load commands.
|
||||
size_t header_size =
|
||||
sizeof(mach_header_type) + header->sizeofcmds;
|
||||
|
||||
// Read the file name from the task's memory space.
|
||||
char *file_path = NULL;
|
||||
if (info.file_path_) {
|
||||
// Although we're reading kMaxStringLength bytes, it's copied in the
|
||||
// the DynamicImage constructor below with the correct string length,
|
||||
// so it's not really wasting memory.
|
||||
file_path = reinterpret_cast<char*>
|
||||
(ReadTaskString(task_, info.file_path_));
|
||||
}
|
||||
if (ReadTaskMemory(images.task_,
|
||||
info.load_address_,
|
||||
header_size,
|
||||
mach_header_bytes) != KERN_SUCCESS)
|
||||
continue;
|
||||
|
||||
// Create an object representing this image and add it to our list.
|
||||
DynamicImage *new_image;
|
||||
new_image = new DynamicImage(header,
|
||||
header_size,
|
||||
(breakpad_mach_header*)info.load_address_,
|
||||
file_path,
|
||||
info.file_mod_date_,
|
||||
task_);
|
||||
header = reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]);
|
||||
|
||||
if (new_image->IsValid()) {
|
||||
image_list_.push_back(DynamicImageRef(new_image));
|
||||
} else {
|
||||
delete new_image;
|
||||
}
|
||||
|
||||
if (file_path) {
|
||||
free(file_path);
|
||||
}
|
||||
// Read the file name from the task's memory space.
|
||||
string file_path;
|
||||
if (info.file_path_) {
|
||||
// Although we're reading kMaxStringLength bytes, it's copied in the
|
||||
// the DynamicImage constructor below with the correct string length,
|
||||
// so it's not really wasting memory.
|
||||
file_path = ReadTaskString(images.task_, info.file_path_);
|
||||
}
|
||||
|
||||
free(dyldInfo);
|
||||
free(infoArray);
|
||||
// Create an object representing this image and add it to our list.
|
||||
DynamicImage *new_image;
|
||||
new_image = new DynamicImage(&mach_header_bytes[0],
|
||||
header_size,
|
||||
info.load_address_,
|
||||
file_path,
|
||||
info.file_mod_date_,
|
||||
images.task_,
|
||||
images.cpu_type_);
|
||||
|
||||
// sorts based on loading address
|
||||
sort(image_list_.begin(), image_list_.end() );
|
||||
// remove duplicates - this happens in certain strange cases
|
||||
// You can see it in DashboardClient when Google Gadgets plugin
|
||||
// is installed. Apple's crash reporter log and gdb "info shared"
|
||||
// both show the same library multiple times at the same address
|
||||
|
||||
vector<DynamicImageRef>::iterator it = unique(image_list_.begin(),
|
||||
image_list_.end() );
|
||||
image_list_.erase(it, image_list_.end());
|
||||
if (new_image->IsValid()) {
|
||||
images.image_list_.push_back(DynamicImageRef(new_image));
|
||||
} else {
|
||||
delete new_image;
|
||||
}
|
||||
}
|
||||
|
||||
// sorts based on loading address
|
||||
sort(images.image_list_.begin(), images.image_list_.end());
|
||||
// remove duplicates - this happens in certain strange cases
|
||||
// You can see it in DashboardClient when Google Gadgets plugin
|
||||
// is installed. Apple's crash reporter log and gdb "info shared"
|
||||
// both show the same library multiple times at the same address
|
||||
|
||||
vector<DynamicImageRef>::iterator it = unique(images.image_list_.begin(),
|
||||
images.image_list_.end());
|
||||
images.image_list_.erase(it, images.image_list_.end());
|
||||
}
|
||||
|
||||
void DynamicImages::ReadImageInfoForTask() {
|
||||
uint64_t imageList = GetDyldAllImageInfosPointer();
|
||||
|
||||
if (imageList) {
|
||||
if (Is64Bit())
|
||||
ReadImageInfo<MachO64>(*this, imageList);
|
||||
else
|
||||
ReadImageInfo<MachO32>(*this, imageList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +533,7 @@ int DynamicImages::GetExecutableImageIndex() {
|
||||
|
||||
for (int i = 0; i < image_count; ++i) {
|
||||
DynamicImage *image = GetImage(i);
|
||||
if (image->GetMachHeader()->filetype == MH_EXECUTE) {
|
||||
if (image->GetFileType() == MH_EXECUTE) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -445,4 +541,27 @@ int DynamicImages::GetExecutableImageIndex() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// static
|
||||
cpu_type_t DynamicImages::DetermineTaskCPUType(task_t task) {
|
||||
if (task == mach_task_self())
|
||||
return GetNativeCPUType();
|
||||
|
||||
int mib[CTL_MAXNAME];
|
||||
size_t mibLen = CTL_MAXNAME;
|
||||
int err = sysctlnametomib("sysctl.proc_cputype", mib, &mibLen);
|
||||
if (err == 0) {
|
||||
assert(mibLen < CTL_MAXNAME);
|
||||
pid_for_task(task, &mib[mibLen]);
|
||||
mibLen += 1;
|
||||
|
||||
cpu_type_t cpu_type;
|
||||
size_t cpuTypeSize = sizeof(cpu_type);
|
||||
sysctl(mib, mibLen, &cpu_type, &cpuTypeSize, 0, 0);
|
||||
return cpu_type;
|
||||
}
|
||||
|
||||
return GetNativeCPUType();
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@@ -41,32 +41,49 @@
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
//==============================================================================
|
||||
// The memory layout of this struct matches the dyld_image_info struct
|
||||
// defined in "dyld_gdb.h" in the darwin source.
|
||||
typedef struct dyld_image_info {
|
||||
struct mach_header *load_address_;
|
||||
char *file_path_;
|
||||
uintptr_t file_mod_date_;
|
||||
} dyld_image_info;
|
||||
typedef struct dyld_image_info32 {
|
||||
uint32_t load_address_; // struct mach_header*
|
||||
uint32_t file_path_; // char*
|
||||
uint32_t file_mod_date_;
|
||||
} dyld_image_info32;
|
||||
|
||||
typedef struct dyld_image_info64 {
|
||||
uint64_t load_address_; // struct mach_header*
|
||||
uint64_t file_path_; // char*
|
||||
uint64_t file_mod_date_;
|
||||
} dyld_image_info64;
|
||||
|
||||
//==============================================================================
|
||||
// This is as defined in "dyld_gdb.h" in the darwin source.
|
||||
// _dyld_all_image_infos (in dyld) is a structure of this type
|
||||
// which will be used to determine which dynamic code has been loaded.
|
||||
typedef struct dyld_all_image_infos {
|
||||
typedef struct dyld_all_image_infos32 {
|
||||
uint32_t version; // == 1 in Mac OS X 10.4
|
||||
uint32_t infoArrayCount;
|
||||
const struct dyld_image_info *infoArray;
|
||||
void* notification;
|
||||
uint32_t infoArray; // const struct dyld_image_info*
|
||||
uint32_t notification;
|
||||
bool processDetachedFromSharedRegion;
|
||||
} dyld_all_image_infos;
|
||||
} dyld_all_image_infos32;
|
||||
|
||||
typedef struct dyld_all_image_infos64 {
|
||||
uint32_t version; // == 1 in Mac OS X 10.4
|
||||
uint32_t infoArrayCount;
|
||||
uint64_t infoArray; // const struct dyld_image_info*
|
||||
uint64_t notification;
|
||||
bool processDetachedFromSharedRegion;
|
||||
} dyld_all_image_infos64;
|
||||
|
||||
// some typedefs to isolate 64/32 bit differences
|
||||
#ifdef __LP64__
|
||||
@@ -77,66 +94,49 @@ typedef mach_header breakpad_mach_header;
|
||||
typedef segment_command breakpad_mach_segment_command;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
// A simple wrapper for a mach_header
|
||||
//
|
||||
// This could be fleshed out with some more interesting methods.
|
||||
class MachHeader {
|
||||
public:
|
||||
explicit MachHeader(const breakpad_mach_header &header) : header_(header) {}
|
||||
// Helper functions to deal with 32-bit/64-bit Mach-O differences.
|
||||
class DynamicImage;
|
||||
template<typename MachBits>
|
||||
bool FindTextSection(DynamicImage& image);
|
||||
|
||||
void Print() {
|
||||
printf("magic\t\t: %4x\n", header_.magic);
|
||||
printf("cputype\t\t: %d\n", header_.cputype);
|
||||
printf("cpusubtype\t: %d\n", header_.cpusubtype);
|
||||
printf("filetype\t: %d\n", header_.filetype);
|
||||
printf("ncmds\t\t: %d\n", header_.ncmds);
|
||||
printf("sizeofcmds\t: %d\n", header_.sizeofcmds);
|
||||
printf("flags\t\t: %d\n", header_.flags);
|
||||
}
|
||||
|
||||
breakpad_mach_header header_;
|
||||
};
|
||||
template<typename MachBits>
|
||||
uint32_t GetFileTypeFromHeader(DynamicImage& image);
|
||||
|
||||
//==============================================================================
|
||||
// Represents a single dynamically loaded mach-o image
|
||||
class DynamicImage {
|
||||
public:
|
||||
DynamicImage(breakpad_mach_header *header, // we take ownership
|
||||
int header_size, // includes load commands
|
||||
breakpad_mach_header *load_address,
|
||||
char *inFilePath,
|
||||
DynamicImage(uint8_t *header, // data is copied
|
||||
size_t header_size, // includes load commands
|
||||
uint64_t load_address,
|
||||
string file_path,
|
||||
uintptr_t image_mod_date,
|
||||
mach_port_t task)
|
||||
: header_(header),
|
||||
mach_port_t task,
|
||||
cpu_type_t cpu_type)
|
||||
: header_(header, header + header_size),
|
||||
header_size_(header_size),
|
||||
load_address_(load_address),
|
||||
vmaddr_(0),
|
||||
vmsize_(0),
|
||||
slide_(0),
|
||||
version_(0),
|
||||
file_path_(file_path),
|
||||
file_mod_date_(image_mod_date),
|
||||
task_(task) {
|
||||
InitializeFilePath(inFilePath);
|
||||
task_(task),
|
||||
cpu_type_(cpu_type) {
|
||||
CalculateMemoryAndVersionInfo();
|
||||
}
|
||||
|
||||
~DynamicImage() {
|
||||
if (file_path_) {
|
||||
free(file_path_);
|
||||
}
|
||||
free(header_);
|
||||
}
|
||||
|
||||
// Returns pointer to a local copy of the mach_header plus load commands
|
||||
breakpad_mach_header *GetMachHeader() {return header_;}
|
||||
|
||||
// Size of mach_header plus load commands
|
||||
int GetHeaderSize() const {return header_size_;}
|
||||
size_t GetHeaderSize() const {return header_.size();}
|
||||
|
||||
// Full path to mach-o binary
|
||||
char *GetFilePath() {return file_path_;}
|
||||
string GetFilePath() {return file_path_;}
|
||||
|
||||
uintptr_t GetModDate() const {return file_mod_date_;}
|
||||
uint64_t GetModDate() const {return file_mod_date_;}
|
||||
|
||||
// Actual address where the image was loaded
|
||||
breakpad_mach_header *GetLoadAddress() const {return load_address_;}
|
||||
uint64_t GetLoadAddress() const {return load_address_;}
|
||||
|
||||
// Address where the image should be loaded
|
||||
mach_vm_address_t GetVMAddr() const {return vmaddr_;}
|
||||
@@ -150,46 +150,49 @@ class DynamicImage {
|
||||
// Task owning this loaded image
|
||||
mach_port_t GetTask() {return task_;}
|
||||
|
||||
// CPU type of the task
|
||||
cpu_type_t GetCPUType() {return cpu_type_;}
|
||||
|
||||
// filetype from the Mach-O header.
|
||||
uint32_t GetFileType();
|
||||
|
||||
// Return true if the task is a 64-bit architecture.
|
||||
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; }
|
||||
|
||||
uint32_t GetVersion() {return version_;}
|
||||
// For sorting
|
||||
bool operator<(const DynamicImage &inInfo) {
|
||||
return GetLoadAddress() < inInfo.GetLoadAddress();
|
||||
}
|
||||
|
||||
// Debugging
|
||||
void Print();
|
||||
|
||||
private:
|
||||
friend class DynamicImages;
|
||||
|
||||
// Sanity checking
|
||||
bool IsValid() {return GetVMSize() != 0;}
|
||||
|
||||
// Makes local copy of file path to mach-o binary
|
||||
void InitializeFilePath(char *inFilePath) {
|
||||
if (inFilePath) {
|
||||
size_t path_size = 1 + strlen(inFilePath);
|
||||
file_path_ = reinterpret_cast<char*>(malloc(path_size));
|
||||
strlcpy(file_path_, inFilePath, path_size);
|
||||
} else {
|
||||
file_path_ = NULL;
|
||||
}
|
||||
}
|
||||
private:
|
||||
DynamicImage(const DynamicImage &);
|
||||
DynamicImage &operator=(const DynamicImage &);
|
||||
|
||||
friend class DynamicImages;
|
||||
template<typename MachBits>
|
||||
friend bool FindTextSection(DynamicImage& image);
|
||||
template<typename MachBits>
|
||||
friend uint32_t GetFileTypeFromHeader(DynamicImage& image);
|
||||
|
||||
// Initializes vmaddr_, vmsize_, and slide_
|
||||
void CalculateMemoryAndVersionInfo();
|
||||
|
||||
breakpad_mach_header *header_; // our local copy of the header
|
||||
int header_size_; // mach_header plus load commands
|
||||
breakpad_mach_header *load_address_; // base address image is mapped into
|
||||
const vector<uint8_t> header_; // our local copy of the header
|
||||
size_t header_size_; // mach_header plus load commands
|
||||
uint64_t load_address_; // base address image is mapped into
|
||||
mach_vm_address_t vmaddr_;
|
||||
mach_vm_size_t vmsize_;
|
||||
ptrdiff_t slide_;
|
||||
uint32_t version_; // Dylib version
|
||||
char *file_path_; // path dyld used to load the image
|
||||
string file_path_; // path dyld used to load the image
|
||||
uintptr_t file_mod_date_; // time_t of image file
|
||||
|
||||
mach_port_t task_;
|
||||
cpu_type_t cpu_type_; // CPU type of task_
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
@@ -222,6 +225,11 @@ class DynamicImageRef {
|
||||
DynamicImage *p;
|
||||
};
|
||||
|
||||
// Helper function to deal with 32-bit/64-bit Mach-O differences.
|
||||
class DynamicImages;
|
||||
template<typename MachBits>
|
||||
void ReadImageInfo(DynamicImages& images, uint64_t image_list_address);
|
||||
|
||||
//==============================================================================
|
||||
// An object of type DynamicImages may be created to allow introspection of
|
||||
// an arbitrary task's dynamically loaded mach-o binaries. This makes the
|
||||
@@ -231,13 +239,13 @@ class DynamicImages {
|
||||
explicit DynamicImages(mach_port_t task);
|
||||
|
||||
~DynamicImages() {
|
||||
for (int i = 0; i < (int)image_list_.size(); ++i) {
|
||||
for (int i = 0; i < GetImageCount(); ++i) {
|
||||
delete image_list_[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the number of dynamically loaded mach-o images.
|
||||
int GetImageCount() const {return image_list_.size();}
|
||||
int GetImageCount() const {return static_cast<int>(image_list_.size());}
|
||||
|
||||
// Returns an individual image.
|
||||
DynamicImage *GetImage(int i) {
|
||||
@@ -254,43 +262,51 @@ class DynamicImages {
|
||||
// Returns the task which we're looking at.
|
||||
mach_port_t GetTask() const {return task_;}
|
||||
|
||||
// Debugging
|
||||
void Print() {
|
||||
for (int i = 0; i < (int)image_list_.size(); ++i) {
|
||||
image_list_[i]->Print();
|
||||
}
|
||||
}
|
||||
// CPU type of the task
|
||||
cpu_type_t GetCPUType() {return cpu_type_;}
|
||||
|
||||
void TestPrint() {
|
||||
const breakpad_mach_header *header;
|
||||
for (int i = 0; i < (int)image_list_.size(); ++i) {
|
||||
printf("dyld: %p: name = %s\n", _dyld_get_image_header(i),
|
||||
_dyld_get_image_name(i) );
|
||||
// Return true if the task is a 64-bit architecture.
|
||||
bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; }
|
||||
|
||||
const void *imageHeader = _dyld_get_image_header(i);
|
||||
header = reinterpret_cast<const breakpad_mach_header*>(imageHeader);
|
||||
// Determine the CPU type of the task being dumped.
|
||||
static cpu_type_t DetermineTaskCPUType(task_t task);
|
||||
|
||||
MachHeader(*header).Print();
|
||||
}
|
||||
// Get the native CPU type of this task.
|
||||
static cpu_type_t GetNativeCPUType() {
|
||||
#if defined(__i386__)
|
||||
return CPU_TYPE_I386;
|
||||
#elif defined(__x86_64__)
|
||||
return CPU_TYPE_X86_64;
|
||||
#elif defined(__ppc__)
|
||||
return CPU_TYPE_POWERPC;
|
||||
#elif defined(__ppc64__)
|
||||
return CPU_TYPE_POWERPC64;
|
||||
#else
|
||||
#error "GetNativeCPUType not implemented for this architecture"
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename MachBits>
|
||||
friend void ReadImageInfo(DynamicImages& images, uint64_t image_list_address);
|
||||
|
||||
bool IsOurTask() {return task_ == mach_task_self();}
|
||||
|
||||
// Initialization
|
||||
void ReadImageInfoForTask();
|
||||
void* GetDyldAllImageInfosPointer();
|
||||
uint64_t GetDyldAllImageInfosPointer();
|
||||
|
||||
mach_port_t task_;
|
||||
cpu_type_t cpu_type_; // CPU type of task_
|
||||
vector<DynamicImageRef> image_list_;
|
||||
};
|
||||
|
||||
// Returns a malloced block containing the contents of memory at a particular
|
||||
// Fill bytes with the contents of memory at a particular
|
||||
// location in another task.
|
||||
void* ReadTaskMemory(task_port_t target_task,
|
||||
const void* address,
|
||||
size_t len,
|
||||
kern_return_t *kr);
|
||||
kern_return_t ReadTaskMemory(task_port_t target_task,
|
||||
const uint64_t address,
|
||||
size_t length,
|
||||
vector<uint8_t> &bytes);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
|
@@ -33,6 +33,7 @@
|
||||
#include "client/mac/handler/exception_handler.h"
|
||||
#include "client/mac/handler/minidump_generator.h"
|
||||
#include "common/mac/macho_utilities.h"
|
||||
#include "common/mac/scoped_task_suspend-inl.h"
|
||||
|
||||
#ifndef USE_PROTECTED_ALLOCATIONS
|
||||
#define USE_PROTECTED_ALLOCATIONS 0
|
||||
@@ -92,12 +93,15 @@ extern "C"
|
||||
boolean_t exc_server(mach_msg_header_t *request,
|
||||
mach_msg_header_t *reply);
|
||||
|
||||
// This symbol must be visible to dlsym() - see
|
||||
// http://code.google.com/p/google-breakpad/issues/detail?id=345 for details.
|
||||
kern_return_t catch_exception_raise(mach_port_t target_port,
|
||||
mach_port_t failed_thread,
|
||||
mach_port_t task,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t code_count);
|
||||
mach_msg_type_number_t code_count)
|
||||
__attribute__((visibility("default")));
|
||||
|
||||
kern_return_t ForwardException(mach_port_t task,
|
||||
mach_port_t failed_thread,
|
||||
@@ -120,10 +124,10 @@ extern "C"
|
||||
exception_data_t exception_code,
|
||||
mach_msg_type_number_t code_count,
|
||||
thread_state_flavor_t *target_flavor,
|
||||
thread_state_t thread_state,
|
||||
mach_msg_type_number_t thread_state_count,
|
||||
thread_state_t thread_state,
|
||||
mach_msg_type_number_t *thread_state_count);
|
||||
thread_state_t in_thread_state,
|
||||
mach_msg_type_number_t in_thread_state_count,
|
||||
thread_state_t out_thread_state,
|
||||
mach_msg_type_number_t *out_thread_state_count);
|
||||
|
||||
kern_return_t
|
||||
exception_raise_state_identity(mach_port_t target_port,
|
||||
@@ -133,10 +137,10 @@ extern "C"
|
||||
exception_data_t exception_code,
|
||||
mach_msg_type_number_t exception_code_count,
|
||||
thread_state_flavor_t *target_flavor,
|
||||
thread_state_t thread_state,
|
||||
mach_msg_type_number_t thread_state_count,
|
||||
thread_state_t thread_state,
|
||||
mach_msg_type_number_t *thread_state_count);
|
||||
thread_state_t in_thread_state,
|
||||
mach_msg_type_number_t in_thread_state_count,
|
||||
thread_state_t out_thread_state,
|
||||
mach_msg_type_number_t *out_thread_state_count);
|
||||
|
||||
kern_return_t breakpad_exception_raise_state(mach_port_t exception_port,
|
||||
exception_type_t exception,
|
||||
@@ -218,7 +222,8 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
FilterCallback filter,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context,
|
||||
bool install_handler)
|
||||
bool install_handler,
|
||||
const char *port_name)
|
||||
: dump_path_(),
|
||||
filter_(filter),
|
||||
callback_(callback),
|
||||
@@ -234,6 +239,8 @@ ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||
// This will update to the ID and C-string pointers
|
||||
set_dump_path(dump_path);
|
||||
MinidumpGenerator::GatherSystemInformation();
|
||||
if (port_name)
|
||||
crash_generation_client_.reset(new CrashGenerationClient(port_name));
|
||||
Setup(install_handler);
|
||||
}
|
||||
|
||||
@@ -262,7 +269,7 @@ ExceptionHandler::~ExceptionHandler() {
|
||||
Teardown();
|
||||
}
|
||||
|
||||
bool ExceptionHandler::WriteMinidump() {
|
||||
bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
|
||||
// If we're currently writing, just return
|
||||
if (use_minidump_write_mutex_)
|
||||
return false;
|
||||
@@ -274,7 +281,9 @@ bool ExceptionHandler::WriteMinidump() {
|
||||
if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
|
||||
// Send an empty message to the handle port so that a minidump will
|
||||
// be written
|
||||
SendEmptyMachMessage();
|
||||
SendMessageToHandlerThread(write_exception_stream ?
|
||||
kWriteDumpWithExceptionMessage :
|
||||
kWriteDumpMessage);
|
||||
|
||||
// Wait for the minidump writer to complete its writing. It will unlock
|
||||
// the mutex when completed
|
||||
@@ -288,16 +297,50 @@ bool ExceptionHandler::WriteMinidump() {
|
||||
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidump(const string &dump_path,
|
||||
bool write_exception_stream,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context) {
|
||||
ExceptionHandler handler(dump_path, NULL, callback, callback_context, false);
|
||||
return handler.WriteMinidump();
|
||||
ExceptionHandler handler(dump_path, NULL, callback, callback_context, false,
|
||||
NULL);
|
||||
return handler.WriteMinidump(write_exception_stream);
|
||||
}
|
||||
|
||||
// static
|
||||
bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
|
||||
mach_port_t child_blamed_thread,
|
||||
const string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context) {
|
||||
ScopedTaskSuspend suspend(child);
|
||||
|
||||
MinidumpGenerator generator(child, MACH_PORT_NULL);
|
||||
string dump_id;
|
||||
string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id);
|
||||
|
||||
generator.SetExceptionInformation(EXC_BREAKPOINT,
|
||||
#if defined (__i386__) || defined(__x86_64__)
|
||||
EXC_I386_BPT,
|
||||
#elif defined (__ppc__) || defined (__ppc64__)
|
||||
EXC_PPC_BREAKPOINT,
|
||||
#else
|
||||
#error architecture not supported
|
||||
#endif
|
||||
0,
|
||||
child_blamed_thread);
|
||||
bool result = generator.Write(dump_filename.c_str());
|
||||
|
||||
if (callback) {
|
||||
return callback(dump_path.c_str(), dump_id.c_str(),
|
||||
callback_context, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t thread_name) {
|
||||
mach_port_t thread_name,
|
||||
bool exit_after_write) {
|
||||
bool result = false;
|
||||
|
||||
if (directCallback_) {
|
||||
@@ -306,9 +349,21 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
exception_code,
|
||||
exception_subcode,
|
||||
thread_name) ) {
|
||||
if (exception_type && exception_code)
|
||||
if (exit_after_write)
|
||||
_exit(exception_type);
|
||||
}
|
||||
} else if (IsOutOfProcess()) {
|
||||
if (exception_type && exception_code) {
|
||||
// If this is a real exception, give the filter (if any) a chance to
|
||||
// decide if this should be sent.
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
return crash_generation_client_->RequestDumpForException(
|
||||
exception_type,
|
||||
exception_code,
|
||||
exception_subcode,
|
||||
thread_name);
|
||||
}
|
||||
} else {
|
||||
string minidump_id;
|
||||
|
||||
@@ -318,7 +373,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
MinidumpGenerator md;
|
||||
if (exception_type && exception_code) {
|
||||
// If this is a real exception, give the filter (if any) a chance to
|
||||
// decided if this should be sent
|
||||
// decide if this should be sent.
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
return false;
|
||||
|
||||
@@ -336,7 +391,7 @@ bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||
// forwarding the exception to the next handler.
|
||||
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
|
||||
result)) {
|
||||
if (exception_type && exception_code)
|
||||
if (exit_after_write)
|
||||
_exit(exception_type);
|
||||
}
|
||||
}
|
||||
@@ -435,6 +490,9 @@ kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
|
||||
exception_type_t exception,
|
||||
exception_data_t code,
|
||||
mach_msg_type_number_t code_count) {
|
||||
if (task != mach_task_self()) {
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
return ForwardException(task, failed_thread, exception, code, code_count);
|
||||
}
|
||||
|
||||
@@ -447,10 +505,11 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
// Wait for the exception info
|
||||
while (1) {
|
||||
receive.header.msgh_local_port = self->handler_port_;
|
||||
receive.header.msgh_size = sizeof(receive);
|
||||
receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive));
|
||||
kern_return_t result = mach_msg(&(receive.header),
|
||||
MACH_RCV_MSG | MACH_RCV_LARGE, 0,
|
||||
sizeof(receive), self->handler_port_,
|
||||
receive.header.msgh_size,
|
||||
self->handler_port_,
|
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
|
||||
|
||||
@@ -467,7 +526,9 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
// to avoid misleading stacks. If appropriate they will be resumed
|
||||
// afterwards.
|
||||
if (!receive.exception) {
|
||||
if (self->is_in_teardown_)
|
||||
// Don't touch self, since this message could have been sent
|
||||
// from its destructor.
|
||||
if (receive.header.msgh_id == kShutdownMessage)
|
||||
return NULL;
|
||||
|
||||
self->SuspendThreads();
|
||||
@@ -477,11 +538,26 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
gBreakpadAllocator->Unprotect();
|
||||
#endif
|
||||
|
||||
mach_port_t thread = MACH_PORT_NULL;
|
||||
int exception_type = 0;
|
||||
int exception_code = 0;
|
||||
if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) {
|
||||
thread = receive.thread.name;
|
||||
exception_type = EXC_BREAKPOINT;
|
||||
#if defined (__i386__) || defined(__x86_64__)
|
||||
exception_code = EXC_I386_BPT;
|
||||
#elif defined (__ppc__) || defined (__ppc64__)
|
||||
exception_code = EXC_PPC_BREAKPOINT;
|
||||
#else
|
||||
#error architecture not supported
|
||||
#endif
|
||||
}
|
||||
|
||||
// Write out the dump and save the result for later retrieval
|
||||
self->last_minidump_write_result_ =
|
||||
self->WriteMinidumpWithException(0, 0, 0, 0);
|
||||
|
||||
self->UninstallHandler(false);
|
||||
self->WriteMinidumpWithException(exception_type, exception_code,
|
||||
0, thread,
|
||||
false);
|
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS
|
||||
if(gBreakpadAllocator)
|
||||
@@ -515,7 +591,15 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
|
||||
// Generate the minidump with the exception data.
|
||||
self->WriteMinidumpWithException(receive.exception, receive.code[0],
|
||||
subcode, receive.thread.name);
|
||||
subcode, receive.thread.name, true);
|
||||
|
||||
#if USE_PROTECTED_ALLOCATIONS
|
||||
// This may have become protected again within
|
||||
// WriteMinidumpWithException, but it needs to be unprotected for
|
||||
// UninstallHandler.
|
||||
if(gBreakpadAllocator)
|
||||
gBreakpadAllocator->Unprotect();
|
||||
#endif
|
||||
|
||||
self->UninstallHandler(true);
|
||||
|
||||
@@ -650,7 +734,7 @@ bool ExceptionHandler::Teardown() {
|
||||
return false;
|
||||
|
||||
// Send an empty message so that the handler_thread exits
|
||||
if (SendEmptyMachMessage()) {
|
||||
if (SendMessageToHandlerThread(kShutdownMessage)) {
|
||||
mach_port_t current_task = mach_task_self();
|
||||
result = mach_port_deallocate(current_task, handler_port_);
|
||||
if (result != KERN_SUCCESS)
|
||||
@@ -666,16 +750,25 @@ bool ExceptionHandler::Teardown() {
|
||||
return result == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
bool ExceptionHandler::SendEmptyMachMessage() {
|
||||
ExceptionMessage empty;
|
||||
memset(&empty, 0, sizeof(empty));
|
||||
empty.header.msgh_size = sizeof(empty) - sizeof(empty.padding);
|
||||
empty.header.msgh_remote_port = handler_port_;
|
||||
empty.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
|
||||
bool ExceptionHandler::SendMessageToHandlerThread(
|
||||
HandlerThreadMessage message_id) {
|
||||
ExceptionMessage msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.header.msgh_id = message_id;
|
||||
if (message_id == kWriteDumpMessage ||
|
||||
message_id == kWriteDumpWithExceptionMessage) {
|
||||
// Include this thread's port.
|
||||
msg.thread.name = mach_thread_self();
|
||||
msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND;
|
||||
msg.thread.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
}
|
||||
msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding);
|
||||
msg.header.msgh_remote_port = handler_port_;
|
||||
msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
|
||||
MACH_MSG_TYPE_MAKE_SEND_ONCE);
|
||||
kern_return_t result = mach_msg(&(empty.header),
|
||||
kern_return_t result = mach_msg(&(msg.header),
|
||||
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
|
||||
empty.header.msgh_size, 0, 0,
|
||||
msg.header.msgh_size, 0, 0,
|
||||
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
|
||||
return result == KERN_SUCCESS;
|
||||
|
@@ -40,12 +40,25 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "client/mac/crash_generation/crash_generation_client.h"
|
||||
#include "processor/scoped_ptr.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
|
||||
struct ExceptionParameters;
|
||||
|
||||
enum HandlerThreadMessage {
|
||||
// Message ID telling the handler thread to write a dump.
|
||||
kWriteDumpMessage = 0,
|
||||
// Message ID telling the handler thread to write a dump and include
|
||||
// an exception stream.
|
||||
kWriteDumpWithExceptionMessage = 1,
|
||||
// Message ID telling the handler thread to quit.
|
||||
kShutdownMessage = 2
|
||||
};
|
||||
|
||||
class ExceptionHandler {
|
||||
public:
|
||||
// A callback function to run before Breakpad performs any substantial
|
||||
@@ -86,9 +99,12 @@ class ExceptionHandler {
|
||||
// If install_handler is true, then a minidump will be written whenever
|
||||
// an unhandled exception occurs. If it is false, minidumps will only
|
||||
// be written when WriteMinidump is called.
|
||||
// If port_name is non-NULL, attempt to perform out-of-process dump generation
|
||||
// If port_name is NULL, in-process dump generation will be used.
|
||||
ExceptionHandler(const string &dump_path,
|
||||
FilterCallback filter, MinidumpCallback callback,
|
||||
void *callback_context, bool install_handler);
|
||||
void *callback_context, bool install_handler,
|
||||
const char *port_name);
|
||||
|
||||
// A special constructor if we want to bypass minidump writing and
|
||||
// simply get a callback with the exception information.
|
||||
@@ -108,13 +124,37 @@ class ExceptionHandler {
|
||||
|
||||
// Writes a minidump immediately. This can be used to capture the
|
||||
// execution state independently of a crash. Returns true on success.
|
||||
bool WriteMinidump();
|
||||
bool WriteMinidump() {
|
||||
return WriteMinidump(false);
|
||||
}
|
||||
|
||||
bool WriteMinidump(bool write_exception_stream);
|
||||
|
||||
// Convenience form of WriteMinidump which does not require an
|
||||
// ExceptionHandler instance.
|
||||
static bool WriteMinidump(const string &dump_path, MinidumpCallback callback,
|
||||
void *callback_context) {
|
||||
return WriteMinidump(dump_path, false, callback, callback_context);
|
||||
}
|
||||
|
||||
static bool WriteMinidump(const string &dump_path,
|
||||
bool write_exception_stream,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context);
|
||||
|
||||
// Write a minidump of child immediately. This can be used to capture
|
||||
// the execution state of a child process independently of a crash.
|
||||
static bool WriteMinidumpForChild(mach_port_t child,
|
||||
mach_port_t child_blamed_thread,
|
||||
const std::string &dump_path,
|
||||
MinidumpCallback callback,
|
||||
void *callback_context);
|
||||
|
||||
// Returns whether out-of-process dump generation is used or not.
|
||||
bool IsOutOfProcess() const {
|
||||
return crash_generation_client_.get() != NULL;
|
||||
}
|
||||
|
||||
private:
|
||||
// Install the mach exception handler
|
||||
bool InstallHandler();
|
||||
@@ -130,13 +170,16 @@ class ExceptionHandler {
|
||||
// thread
|
||||
bool Teardown();
|
||||
|
||||
// Send an "empty" mach message to the exception handler. Return true on
|
||||
// success, false otherwise
|
||||
bool SendEmptyMachMessage();
|
||||
// Send a mach message to the exception handler. Return true on
|
||||
// success, false otherwise.
|
||||
bool SendMessageToHandlerThread(HandlerThreadMessage message_id);
|
||||
|
||||
// All minidump writing goes through this one routine
|
||||
bool WriteMinidumpWithException(int exception_type, int exception_code,
|
||||
int exception_subcode, mach_port_t thread_name);
|
||||
bool WriteMinidumpWithException(int exception_type,
|
||||
int exception_code,
|
||||
int exception_subcode,
|
||||
mach_port_t thread_name,
|
||||
bool exit_after_write);
|
||||
|
||||
// When installed, this static function will be call from a newly created
|
||||
// pthread with |this| as the argument
|
||||
@@ -206,6 +249,9 @@ class ExceptionHandler {
|
||||
|
||||
// True, if we're using the mutext to indicate when mindump writing occurs
|
||||
bool use_minidump_write_mutex_;
|
||||
|
||||
// Client for out-of-process dump generation.
|
||||
scoped_ptr<CrashGenerationClient> crash_generation_client_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@@ -1,99 +0,0 @@
|
||||
// Copyright (c) 2006, 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.
|
||||
|
||||
/*
|
||||
g++ -framework CoreFoundation -I../../.. ../../minidump_file_writer.cc ../../../common/convert_UTF.c ../../../common/string_conversion.cc ../../../common/mac/string_utilities.cc exception_handler.cc minidump_generator.cc exception_handler_test.cc -o exception_handler_test -mmacosx-version-min=10.4 ../../../common/mac/file_id.cc dynamic_images.cc ../../../common/mac/macho_id.cc ../../../common/mac/macho_walker.cc -lcrypto ../../../common/mac/macho_utilities.cc
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include "exception_handler.h"
|
||||
#include "minidump_generator.h"
|
||||
|
||||
using std::string;
|
||||
using google_breakpad::ExceptionHandler;
|
||||
|
||||
static void *SleepyFunction(void *) {
|
||||
while (1) {
|
||||
sleep(10000);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void Crasher() {
|
||||
int *a = (int*)0x42;
|
||||
|
||||
fprintf(stdout, "Going to crash...\n");
|
||||
fprintf(stdout, "A = %d", *a);
|
||||
}
|
||||
|
||||
static void SoonToCrash() {
|
||||
Crasher();
|
||||
}
|
||||
|
||||
bool MDCallback(const char *dump_dir, const char *file_name,
|
||||
void *context, bool success) {
|
||||
string path(dump_dir);
|
||||
string dest(dump_dir);
|
||||
path.append(file_name);
|
||||
path.append(".dmp");
|
||||
|
||||
fprintf(stdout, "Minidump: %s\n", path.c_str());
|
||||
// Indicate that we've handled the callback
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char * const argv[]) {
|
||||
char buffer[PATH_MAX];
|
||||
|
||||
// Home dir
|
||||
snprintf(buffer, sizeof(buffer), "/tmp/");
|
||||
|
||||
string path(buffer);
|
||||
ExceptionHandler eh(path, NULL, MDCallback, NULL, true);
|
||||
pthread_t t;
|
||||
|
||||
if (pthread_create(&t, NULL, SleepyFunction, NULL) == 0) {
|
||||
pthread_detach(t);
|
||||
} else {
|
||||
perror("pthread_create");
|
||||
}
|
||||
|
||||
// // Dump a test
|
||||
// eh.WriteMinidump();
|
||||
|
||||
// Test the handler
|
||||
SoonToCrash();
|
||||
|
||||
return 0;
|
||||
}
|
@@ -27,20 +27,26 @@
|
||||
// (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 <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include <mach/host_info.h>
|
||||
#include <mach/i386/thread_status.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <mach/vm_statistics.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <mach/mach_vm.h>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include "client/mac/handler/minidump_generator.h"
|
||||
|
||||
#ifdef HAS_PPC_SUPPORT
|
||||
#include <mach/ppc/thread_status.h>
|
||||
#endif
|
||||
|
||||
#include "client/minidump_file_writer-inl.h"
|
||||
#include "common/mac/file_id.h"
|
||||
#include "common/mac/string_utilities.h"
|
||||
@@ -50,15 +56,24 @@ using MacStringUtils::IntegerValueAtIndex;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
#if __LP64__
|
||||
#define LC_SEGMENT_ARCH LC_SEGMENT_64
|
||||
#else
|
||||
#define LC_SEGMENT_ARCH LC_SEGMENT
|
||||
#endif
|
||||
|
||||
// constructor when generating from within the crashed process
|
||||
MinidumpGenerator::MinidumpGenerator()
|
||||
: exception_type_(0),
|
||||
: writer_(),
|
||||
exception_type_(0),
|
||||
exception_code_(0),
|
||||
exception_subcode_(0),
|
||||
exception_thread_(0),
|
||||
crashing_task_(mach_task_self()),
|
||||
handler_thread_(mach_thread_self()),
|
||||
dynamic_images_(NULL) {
|
||||
cpu_type_(DynamicImages::GetNativeCPUType()),
|
||||
dynamic_images_(NULL),
|
||||
memory_blocks_(&allocator_) {
|
||||
GatherSystemInformation();
|
||||
}
|
||||
|
||||
@@ -66,16 +81,22 @@ MinidumpGenerator::MinidumpGenerator()
|
||||
// crashed process
|
||||
MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
|
||||
mach_port_t handler_thread)
|
||||
: exception_type_(0),
|
||||
: writer_(),
|
||||
exception_type_(0),
|
||||
exception_code_(0),
|
||||
exception_subcode_(0),
|
||||
exception_thread_(0),
|
||||
crashing_task_(crashing_task),
|
||||
handler_thread_(handler_thread) {
|
||||
handler_thread_(handler_thread),
|
||||
cpu_type_(DynamicImages::GetNativeCPUType()),
|
||||
dynamic_images_(NULL),
|
||||
memory_blocks_(&allocator_) {
|
||||
if (crashing_task != mach_task_self()) {
|
||||
dynamic_images_ = new DynamicImages(crashing_task_);
|
||||
cpu_type_ = dynamic_images_->GetCPUType();
|
||||
} else {
|
||||
dynamic_images_ = NULL;
|
||||
cpu_type_ = DynamicImages::GetNativeCPUType();
|
||||
}
|
||||
|
||||
GatherSystemInformation();
|
||||
@@ -165,6 +186,7 @@ string MinidumpGenerator::UniqueNameInDirectory(const string &dir,
|
||||
bool MinidumpGenerator::Write(const char *path) {
|
||||
WriteStreamFN writers[] = {
|
||||
&MinidumpGenerator::WriteThreadListStream,
|
||||
&MinidumpGenerator::WriteMemoryListStream,
|
||||
&MinidumpGenerator::WriteSystemInfoStream,
|
||||
&MinidumpGenerator::WriteModuleListStream,
|
||||
&MinidumpGenerator::WriteMiscInfoStream,
|
||||
@@ -186,7 +208,7 @@ bool MinidumpGenerator::Write(const char *path) {
|
||||
if (!header.Allocate())
|
||||
return false;
|
||||
|
||||
int writer_count = sizeof(writers) / sizeof(writers[0]);
|
||||
int writer_count = static_cast<int>(sizeof(writers) / sizeof(writers[0]));
|
||||
|
||||
// If we don't have exception information, don't write out the
|
||||
// exception stream
|
||||
@@ -233,35 +255,45 @@ size_t MinidumpGenerator::CalculateStackSize(mach_vm_address_t start_addr) {
|
||||
kern_return_t result =
|
||||
mach_vm_region_recurse(crashing_task_, &stack_region_base,
|
||||
&stack_region_size, &nesting_level,
|
||||
region_info,
|
||||
&info_count);
|
||||
region_info, &info_count);
|
||||
|
||||
if (start_addr < stack_region_base) {
|
||||
// probably stack corruption, since mach_vm_region had to go
|
||||
if (result != KERN_SUCCESS || start_addr < stack_region_base) {
|
||||
// Failure or stack corruption, since mach_vm_region had to go
|
||||
// higher in the process address space to find a valid region.
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int tag = submap_info.user_tag;
|
||||
|
||||
if ((stack_region_base + stack_region_size) == TOP_OF_THREAD0_STACK) {
|
||||
// The stack for thread 0 needs to extend all the way to
|
||||
// 0xc0000000 on 32 bit and 00007fff5fc00000 on 64bit. HOWEVER,
|
||||
// for many processes, the stack is first created in one page
|
||||
// below this, and is then later extended to a much larger size by
|
||||
// creating a new VM region immediately below the initial page.
|
||||
// If the user tag is VM_MEMORY_STACK, look for more readable regions with
|
||||
// the same tag placed immediately above the computed stack region. Under
|
||||
// some circumstances, the stack for thread 0 winds up broken up into
|
||||
// multiple distinct abutting regions. This can happen for several reasons,
|
||||
// including user code that calls setrlimit(RLIMIT_STACK, ...) or changes
|
||||
// the access on stack pages by calling mprotect.
|
||||
if (tag == VM_MEMORY_STACK) {
|
||||
while (true) {
|
||||
mach_vm_address_t next_region_base = stack_region_base +
|
||||
stack_region_size;
|
||||
mach_vm_address_t proposed_next_region_base = next_region_base;
|
||||
mach_vm_size_t next_region_size;
|
||||
nesting_level = 0;
|
||||
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
||||
result = mach_vm_region_recurse(crashing_task_, &next_region_base,
|
||||
&next_region_size, &nesting_level,
|
||||
region_info, &info_count);
|
||||
if (result != KERN_SUCCESS ||
|
||||
next_region_base != proposed_next_region_base ||
|
||||
submap_info.user_tag != tag ||
|
||||
(submap_info.protection & VM_PROT_READ) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// You can see this for yourself by running vmmap on a "hello,
|
||||
// world" program
|
||||
|
||||
// Because of the above, we'll add 4k to include the original
|
||||
// stack frame page.
|
||||
// This method of finding the stack region needs to be done in
|
||||
// a better way; the breakpad issue 247 is tracking this.
|
||||
stack_region_size += 0x1000;
|
||||
stack_region_size += next_region_size;
|
||||
}
|
||||
}
|
||||
|
||||
return result == KERN_SUCCESS ?
|
||||
stack_region_base + stack_region_size - start_addr : 0;
|
||||
return stack_region_base + stack_region_size - start_addr;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteStackFromStartAddress(
|
||||
@@ -293,20 +325,15 @@ bool MinidumpGenerator::WriteStackFromStartAddress(
|
||||
return false;
|
||||
|
||||
if (dynamic_images_) {
|
||||
|
||||
kern_return_t kr;
|
||||
|
||||
void *stack_memory = ReadTaskMemory(crashing_task_,
|
||||
(void*)start_addr,
|
||||
size,
|
||||
&kr);
|
||||
|
||||
if (stack_memory == NULL) {
|
||||
vector<uint8_t> stack_memory;
|
||||
if (ReadTaskMemory(crashing_task_,
|
||||
start_addr,
|
||||
size,
|
||||
stack_memory) != KERN_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = memory.Copy(stack_memory, size);
|
||||
free(stack_memory);
|
||||
result = memory.Copy(&stack_memory[0], size);
|
||||
} else {
|
||||
result = memory.Copy(reinterpret_cast<const void *>(start_addr), size);
|
||||
}
|
||||
@@ -318,51 +345,110 @@ bool MinidumpGenerator::WriteStackFromStartAddress(
|
||||
return result;
|
||||
}
|
||||
|
||||
#if TARGET_CPU_PPC || TARGET_CPU_PPC64
|
||||
bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
#if TARGET_CPU_PPC
|
||||
mach_vm_address_t start_addr = machine_state->r1;
|
||||
#else
|
||||
mach_vm_address_t start_addr = machine_state->__r1;
|
||||
#endif
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_PPC
|
||||
return machine_state->srr0;
|
||||
#else
|
||||
return machine_state->__srr0;
|
||||
switch (cpu_type_) {
|
||||
#ifdef HAS_PPC_SUPPORT
|
||||
case CPU_TYPE_POWERPC:
|
||||
return WriteStackPPC(state, stack_location);
|
||||
case CPU_TYPE_POWERPC64:
|
||||
return WriteStackPPC64(state, stack_location);
|
||||
#endif
|
||||
case CPU_TYPE_I386:
|
||||
return WriteStackX86(state, stack_location);
|
||||
case CPU_TYPE_X86_64:
|
||||
return WriteStackX86_64(state, stack_location);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location) {
|
||||
TypedMDRVA<MinidumpContext> context(&writer_);
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
switch (cpu_type_) {
|
||||
#ifdef HAS_PPC_SUPPORT
|
||||
case CPU_TYPE_POWERPC:
|
||||
return WriteContextPPC(state, register_location);
|
||||
case CPU_TYPE_POWERPC64:
|
||||
return WriteContextPPC64(state, register_location);
|
||||
#endif
|
||||
case CPU_TYPE_I386:
|
||||
return WriteContextX86(state, register_location);
|
||||
case CPU_TYPE_X86_64:
|
||||
return WriteContextX86_64(state, register_location);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
u_int64_t MinidumpGenerator::CurrentPCForStack(
|
||||
breakpad_thread_state_data_t state) {
|
||||
switch (cpu_type_) {
|
||||
#ifdef HAS_PPC_SUPPORT
|
||||
case CPU_TYPE_POWERPC:
|
||||
return CurrentPCForStackPPC(state);
|
||||
case CPU_TYPE_POWERPC64:
|
||||
return CurrentPCForStackPPC64(state);
|
||||
#endif
|
||||
case CPU_TYPE_I386:
|
||||
return CurrentPCForStackX86(state);
|
||||
case CPU_TYPE_X86_64:
|
||||
return CurrentPCForStackX86_64(state);
|
||||
default:
|
||||
assert("Unknown CPU type!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_PCC_SUPPORT
|
||||
bool MinidumpGenerator::WriteStackPPC(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
ppc_thread_state_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1);
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteStackPPC64(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
ppc_thread_state64_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state64_t *>(state);
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1);
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStackPPC(breakpad_thread_state_data_t state) {
|
||||
ppc_thread_state_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, srr0);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStackPPC64(breakpad_thread_state_data_t state) {
|
||||
ppc_thread_state64_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state64_t *>(state);
|
||||
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, srr0);
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContextPPC(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location)
|
||||
{
|
||||
TypedMDRVA<MDRawContextPPC> context(&writer_);
|
||||
ppc_thread_state_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
|
||||
*register_location = context.location();
|
||||
MinidumpContext *context_ptr = context.get();
|
||||
MDRawContextPPC *context_ptr = context.get();
|
||||
context_ptr->context_flags = MD_CONTEXT_PPC_BASE;
|
||||
|
||||
#if TARGET_CPU_PPC64
|
||||
#define AddReg(a) context_ptr->a = machine_state->__ ## a
|
||||
#define AddGPR(a) context_ptr->gpr[a] = machine_state->__r ## a
|
||||
#else
|
||||
#define AddReg(a) context_ptr->a = machine_state->a
|
||||
#define AddGPR(a) context_ptr->gpr[a] = machine_state->r ## a
|
||||
#endif
|
||||
#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a)
|
||||
#define AddGPR(a) context_ptr->gpr[a] = REGISTER_FROM_THREADSTATE(machine_state, r ## a)
|
||||
|
||||
AddReg(srr0);
|
||||
AddReg(cr);
|
||||
@@ -403,57 +489,126 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
AddGPR(29);
|
||||
AddGPR(30);
|
||||
AddGPR(31);
|
||||
|
||||
#if TARGET_CPU_PPC
|
||||
/* The mq register is only for PPC */
|
||||
AddReg(mq);
|
||||
#endif
|
||||
|
||||
#undef AddReg
|
||||
#undef AddGPR
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif TARGET_CPU_X86 || TARGET_CPU_X86_64
|
||||
|
||||
bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, rsp);
|
||||
#else
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp);
|
||||
#endif
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStack(breakpad_thread_state_data_t state) {
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, rip);
|
||||
#else
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, eip);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location) {
|
||||
TypedMDRVA<MinidumpContext> context(&writer_);
|
||||
breakpad_thread_state_t *machine_state =
|
||||
reinterpret_cast<breakpad_thread_state_t *>(state);
|
||||
bool MinidumpGenerator::WriteContextPPC64(
|
||||
breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location) {
|
||||
TypedMDRVA<MDRawContextPPC64> context(&writer_);
|
||||
ppc_thread_state64_t *machine_state =
|
||||
reinterpret_cast<ppc_thread_state64_t *>(state);
|
||||
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
|
||||
*register_location = context.location();
|
||||
MinidumpContext *context_ptr = context.get();
|
||||
MDRawContextPPC64 *context_ptr = context.get();
|
||||
context_ptr->context_flags = MD_CONTEXT_PPC_BASE;
|
||||
|
||||
#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a)
|
||||
#if TARGET_CPU_X86
|
||||
#define AddGPR(a) context_ptr->gpr[a] = REGISTER_FROM_THREADSTATE(machine_state, r ## a)
|
||||
|
||||
AddReg(srr0);
|
||||
AddReg(cr);
|
||||
AddReg(xer);
|
||||
AddReg(ctr);
|
||||
AddReg(lr);
|
||||
AddReg(vrsave);
|
||||
|
||||
AddGPR(0);
|
||||
AddGPR(1);
|
||||
AddGPR(2);
|
||||
AddGPR(3);
|
||||
AddGPR(4);
|
||||
AddGPR(5);
|
||||
AddGPR(6);
|
||||
AddGPR(7);
|
||||
AddGPR(8);
|
||||
AddGPR(9);
|
||||
AddGPR(10);
|
||||
AddGPR(11);
|
||||
AddGPR(12);
|
||||
AddGPR(13);
|
||||
AddGPR(14);
|
||||
AddGPR(15);
|
||||
AddGPR(16);
|
||||
AddGPR(17);
|
||||
AddGPR(18);
|
||||
AddGPR(19);
|
||||
AddGPR(20);
|
||||
AddGPR(21);
|
||||
AddGPR(22);
|
||||
AddGPR(23);
|
||||
AddGPR(24);
|
||||
AddGPR(25);
|
||||
AddGPR(26);
|
||||
AddGPR(27);
|
||||
AddGPR(28);
|
||||
AddGPR(29);
|
||||
AddGPR(30);
|
||||
AddGPR(31);
|
||||
#undef AddReg
|
||||
#undef AddGPR
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool MinidumpGenerator::WriteStackX86(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
i386_thread_state_t *machine_state =
|
||||
reinterpret_cast<i386_thread_state_t *>(state);
|
||||
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp);
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteStackX86_64(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location) {
|
||||
x86_thread_state64_t *machine_state =
|
||||
reinterpret_cast<x86_thread_state64_t *>(state);
|
||||
|
||||
mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, rsp);
|
||||
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) {
|
||||
i386_thread_state_t *machine_state =
|
||||
reinterpret_cast<i386_thread_state_t *>(state);
|
||||
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, eip);
|
||||
}
|
||||
|
||||
u_int64_t
|
||||
MinidumpGenerator::CurrentPCForStackX86_64(breakpad_thread_state_data_t state) {
|
||||
x86_thread_state64_t *machine_state =
|
||||
reinterpret_cast<x86_thread_state64_t *>(state);
|
||||
|
||||
return REGISTER_FROM_THREADSTATE(machine_state, rip);
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContextX86(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location)
|
||||
{
|
||||
TypedMDRVA<MDRawContextX86> context(&writer_);
|
||||
i386_thread_state_t *machine_state =
|
||||
reinterpret_cast<i386_thread_state_t *>(state);
|
||||
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
|
||||
*register_location = context.location();
|
||||
MDRawContextX86 *context_ptr = context.get();
|
||||
|
||||
#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a)
|
||||
|
||||
context_ptr->context_flags = MD_CONTEXT_X86;
|
||||
AddReg(eax);
|
||||
AddReg(ebx);
|
||||
@@ -473,7 +628,26 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
AddReg(eflags);
|
||||
|
||||
AddReg(eip);
|
||||
#else
|
||||
#undef AddReg
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteContextX86_64(
|
||||
breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location) {
|
||||
TypedMDRVA<MDRawContextAMD64> context(&writer_);
|
||||
x86_thread_state64_t *machine_state =
|
||||
reinterpret_cast<x86_thread_state64_t *>(state);
|
||||
|
||||
if (!context.Allocate())
|
||||
return false;
|
||||
|
||||
*register_location = context.location();
|
||||
MDRawContextAMD64 *context_ptr = context.get();
|
||||
|
||||
#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a)
|
||||
|
||||
context_ptr->context_flags = MD_CONTEXT_AMD64;
|
||||
AddReg(rax);
|
||||
AddReg(rbx);
|
||||
@@ -496,28 +670,53 @@ bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state,
|
||||
// not used in the flags register. Since the minidump format
|
||||
// specifies 32 bits for the flags register, we can truncate safely
|
||||
// with no loss.
|
||||
context_ptr->eflags = machine_state->__rflags;
|
||||
context_ptr->eflags = static_cast<u_int32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags));
|
||||
AddReg(cs);
|
||||
AddReg(fs);
|
||||
AddReg(gs);
|
||||
#endif
|
||||
#undef AddReg(a)
|
||||
#undef AddReg
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::GetThreadState(thread_act_t target_thread,
|
||||
thread_state_t state,
|
||||
mach_msg_type_number_t *count) {
|
||||
thread_state_flavor_t flavor;
|
||||
switch (cpu_type_) {
|
||||
#ifdef HAS_PPC_SUPPORT
|
||||
case CPU_TYPE_POWERPC:
|
||||
flavor = PPC_THREAD_STATE;
|
||||
break;
|
||||
case CPU_TYPE_POWERPC64:
|
||||
flavor = PPC_THREAD_STATE64;
|
||||
break;
|
||||
#endif
|
||||
case CPU_TYPE_I386:
|
||||
flavor = i386_THREAD_STATE;
|
||||
break;
|
||||
case CPU_TYPE_X86_64:
|
||||
flavor = x86_THREAD_STATE64;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return thread_get_state(target_thread, flavor,
|
||||
state, count) == KERN_SUCCESS;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id,
|
||||
MDRawThread *thread) {
|
||||
breakpad_thread_state_data_t state;
|
||||
mach_msg_type_number_t state_count = sizeof(state);
|
||||
mach_msg_type_number_t state_count
|
||||
= static_cast<mach_msg_type_number_t>(sizeof(state));
|
||||
|
||||
if (thread_get_state(thread_id, BREAKPAD_MACHINE_THREAD_STATE,
|
||||
state, &state_count) ==
|
||||
KERN_SUCCESS) {
|
||||
if (GetThreadState(thread_id, state, &state_count)) {
|
||||
if (!WriteStack(state, &thread->stack))
|
||||
return false;
|
||||
|
||||
memory_blocks_.push_back(thread->stack);
|
||||
|
||||
if (!WriteContext(state, &thread->thread_context))
|
||||
return false;
|
||||
|
||||
@@ -540,7 +739,10 @@ bool MinidumpGenerator::WriteThreadListStream(
|
||||
return false;
|
||||
|
||||
// Don't include the generator thread
|
||||
non_generator_thread_count = thread_count - 1;
|
||||
if (handler_thread_ != MACH_PORT_NULL)
|
||||
non_generator_thread_count = thread_count - 1;
|
||||
else
|
||||
non_generator_thread_count = thread_count;
|
||||
if (!list.AllocateObjectAndArray(non_generator_thread_count,
|
||||
sizeof(MDRawThread)))
|
||||
return false;
|
||||
@@ -567,6 +769,112 @@ bool MinidumpGenerator::WriteThreadListStream(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MinidumpGenerator::WriteMemoryListStream(
|
||||
MDRawDirectory *memory_list_stream) {
|
||||
TypedMDRVA<MDRawMemoryList> list(&writer_);
|
||||
|
||||
// If the dump has an exception, include some memory around the
|
||||
// instruction pointer.
|
||||
const size_t kIPMemorySize = 256; // bytes
|
||||
bool have_ip_memory = false;
|
||||
MDMemoryDescriptor ip_memory_d;
|
||||
if (exception_thread_ && exception_type_) {
|
||||
breakpad_thread_state_data_t state;
|
||||
mach_msg_type_number_t stateCount
|
||||
= static_cast<mach_msg_type_number_t>(sizeof(state));
|
||||
|
||||
if (thread_get_state(exception_thread_,
|
||||
BREAKPAD_MACHINE_THREAD_STATE,
|
||||
state,
|
||||
&stateCount) == KERN_SUCCESS) {
|
||||
u_int64_t ip = CurrentPCForStack(state);
|
||||
// Bound it to the upper and lower bounds of the region
|
||||
// it's contained within. If it's not in a known memory region,
|
||||
// don't bother trying to write it.
|
||||
mach_vm_address_t addr = ip;
|
||||
mach_vm_size_t size;
|
||||
natural_t nesting_level = 0;
|
||||
vm_region_submap_info_64 info;
|
||||
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
||||
|
||||
kern_return_t ret =
|
||||
mach_vm_region_recurse(crashing_task_,
|
||||
&addr,
|
||||
&size,
|
||||
&nesting_level,
|
||||
(vm_region_recurse_info_t)&info,
|
||||
&info_count);
|
||||
if (ret == KERN_SUCCESS && ip >= addr && ip < (addr + size)) {
|
||||
// Try to get 128 bytes before and after the IP, but
|
||||
// settle for whatever's available.
|
||||
ip_memory_d.start_of_memory_range =
|
||||
std::max(uintptr_t(addr),
|
||||
uintptr_t(ip - (kIPMemorySize / 2)));
|
||||
uintptr_t end_of_range =
|
||||
std::min(uintptr_t(ip + (kIPMemorySize / 2)),
|
||||
uintptr_t(addr + size));
|
||||
ip_memory_d.memory.data_size =
|
||||
end_of_range - ip_memory_d.start_of_memory_range;
|
||||
have_ip_memory = true;
|
||||
// This needs to get appended to the list even though
|
||||
// the memory bytes aren't filled in yet so the entire
|
||||
// list can be written first. The memory bytes will get filled
|
||||
// in after the memory list is written.
|
||||
memory_blocks_.push_back(ip_memory_d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now fill in the memory list and write it.
|
||||
unsigned memory_count = memory_blocks_.size();
|
||||
if (!list.AllocateObjectAndArray(memory_count,
|
||||
sizeof(MDMemoryDescriptor)))
|
||||
return false;
|
||||
|
||||
memory_list_stream->stream_type = MD_MEMORY_LIST_STREAM;
|
||||
memory_list_stream->location = list.location();
|
||||
|
||||
list.get()->number_of_memory_ranges = memory_count;
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < memory_count; ++i) {
|
||||
list.CopyIndexAfterObject(i, &memory_blocks_[i],
|
||||
sizeof(MDMemoryDescriptor));
|
||||
}
|
||||
|
||||
if (have_ip_memory) {
|
||||
// Now read the memory around the instruction pointer.
|
||||
UntypedMDRVA ip_memory(&writer_);
|
||||
if (!ip_memory.Allocate(ip_memory_d.memory.data_size))
|
||||
return false;
|
||||
|
||||
if (dynamic_images_) {
|
||||
// Out-of-process.
|
||||
vector<uint8_t> memory;
|
||||
if (ReadTaskMemory(crashing_task_,
|
||||
ip_memory_d.start_of_memory_range,
|
||||
ip_memory_d.memory.data_size,
|
||||
memory) != KERN_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ip_memory.Copy(&memory[0], ip_memory_d.memory.data_size);
|
||||
} else {
|
||||
// In-process, just copy from local memory.
|
||||
ip_memory.Copy(
|
||||
reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range),
|
||||
ip_memory_d.memory.data_size);
|
||||
}
|
||||
|
||||
ip_memory_d.memory = ip_memory.location();
|
||||
// Write this again now that the data location is filled in.
|
||||
list.CopyIndexAfterObject(i - 1, &ip_memory_d,
|
||||
sizeof(MDMemoryDescriptor));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
|
||||
TypedMDRVA<MDRawExceptionStream> exception(&writer_);
|
||||
@@ -585,12 +893,10 @@ MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
|
||||
exception_ptr->exception_record.exception_flags = exception_code_;
|
||||
|
||||
breakpad_thread_state_data_t state;
|
||||
mach_msg_type_number_t stateCount = sizeof(state);
|
||||
mach_msg_type_number_t state_count
|
||||
= static_cast<mach_msg_type_number_t>(sizeof(state));
|
||||
|
||||
if (thread_get_state(exception_thread_,
|
||||
BREAKPAD_MACHINE_THREAD_STATE,
|
||||
state,
|
||||
&stateCount) != KERN_SUCCESS)
|
||||
if (!GetThreadState(exception_thread_, state, &state_count))
|
||||
return false;
|
||||
|
||||
if (!WriteContext(state, &exception_ptr->thread_context))
|
||||
@@ -615,20 +921,22 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
||||
system_info_stream->location = info.location();
|
||||
|
||||
// CPU Information
|
||||
uint32_t cpu_type;
|
||||
size_t len = sizeof(cpu_type);
|
||||
sysctlbyname("hw.cputype", &cpu_type, &len, NULL, 0);
|
||||
uint32_t number_of_processors;
|
||||
len = sizeof(number_of_processors);
|
||||
size_t len = sizeof(number_of_processors);
|
||||
sysctlbyname("hw.ncpu", &number_of_processors, &len, NULL, 0);
|
||||
MDRawSystemInfo *info_ptr = info.get();
|
||||
|
||||
switch (cpu_type) {
|
||||
switch (cpu_type_) {
|
||||
case CPU_TYPE_POWERPC:
|
||||
case CPU_TYPE_POWERPC64:
|
||||
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_PPC;
|
||||
break;
|
||||
case CPU_TYPE_I386:
|
||||
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_X86;
|
||||
case CPU_TYPE_X86_64:
|
||||
if (cpu_type_ == CPU_TYPE_I386)
|
||||
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_X86;
|
||||
else
|
||||
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_AMD64;
|
||||
#ifdef __i386__
|
||||
// ebx is used for PIC code, so we need
|
||||
// to preserve it.
|
||||
@@ -642,6 +950,18 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
||||
"=c" (ecx), \
|
||||
"=d" (edx) \
|
||||
: "0" (op))
|
||||
#elif defined(__x86_64__)
|
||||
|
||||
#define cpuid(op,eax,ebx,ecx,edx) \
|
||||
asm ("cpuid \n\t" \
|
||||
: "=a" (eax), \
|
||||
"=b" (ebx), \
|
||||
"=c" (ecx), \
|
||||
"=d" (edx) \
|
||||
: "0" (op))
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
int unused, unused2;
|
||||
// get vendor id
|
||||
cpuid(0, unused, info_ptr->cpu.x86_cpu_info.vendor_id[0],
|
||||
@@ -650,6 +970,7 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
||||
// get version and feature info
|
||||
cpuid(1, info_ptr->cpu.x86_cpu_info.version_information, unused, unused2,
|
||||
info_ptr->cpu.x86_cpu_info.feature_information);
|
||||
|
||||
// family
|
||||
info_ptr->processor_level =
|
||||
(info_ptr->cpu.x86_cpu_info.version_information & 0xF00) >> 8;
|
||||
@@ -657,7 +978,21 @@ bool MinidumpGenerator::WriteSystemInfoStream(
|
||||
info_ptr->processor_revision =
|
||||
(info_ptr->cpu.x86_cpu_info.version_information & 0xF) |
|
||||
((info_ptr->cpu.x86_cpu_info.version_information & 0xF0) << 4);
|
||||
#endif // __i386__
|
||||
|
||||
// decode extended model info
|
||||
if (info_ptr->processor_level == 0xF ||
|
||||
info_ptr->processor_level == 0x6) {
|
||||
info_ptr->processor_revision |=
|
||||
((info_ptr->cpu.x86_cpu_info.version_information & 0xF0000) >> 4);
|
||||
}
|
||||
|
||||
// decode extended family info
|
||||
if (info_ptr->processor_level == 0xF) {
|
||||
info_ptr->processor_level +=
|
||||
((info_ptr->cpu.x86_cpu_info.version_information & 0xFF00000) >> 20);
|
||||
}
|
||||
|
||||
#endif // __i386__ || __x86_64_
|
||||
break;
|
||||
default:
|
||||
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN;
|
||||
@@ -690,23 +1025,16 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
if (!image)
|
||||
return false;
|
||||
|
||||
const breakpad_mach_header *header = image->GetMachHeader();
|
||||
|
||||
if (!header)
|
||||
return false;
|
||||
|
||||
int cpu_type = header->cputype;
|
||||
|
||||
memset(module, 0, sizeof(MDRawModule));
|
||||
|
||||
MDLocationDescriptor string_location;
|
||||
|
||||
const char* name = image->GetFilePath();
|
||||
if (!writer_.WriteString(name, 0, &string_location))
|
||||
string name = image->GetFilePath();
|
||||
if (!writer_.WriteString(name.c_str(), 0, &string_location))
|
||||
return false;
|
||||
|
||||
module->base_of_image = image->GetVMAddr() + image->GetVMAddrSlide();
|
||||
module->size_of_image = image->GetVMSize();
|
||||
module->size_of_image = static_cast<u_int32_t>(image->GetVMSize());
|
||||
module->module_name_rva = string_location.rva;
|
||||
|
||||
// We'll skip the executable module, because they don't have
|
||||
@@ -729,12 +1057,11 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
module->version_info.file_version_lo |= (modVersion & 0xff);
|
||||
}
|
||||
|
||||
if (!WriteCVRecord(module, cpu_type, name)) {
|
||||
if (!WriteCVRecord(module, image->GetCPUType(), name.c_str())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we're getting module info in the crashed process
|
||||
|
||||
// Getting module info in the crashed process
|
||||
const breakpad_mach_header *header;
|
||||
header = (breakpad_mach_header*)_dyld_get_image_header(index);
|
||||
if (!header)
|
||||
@@ -756,15 +1083,15 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
unsigned long slide = _dyld_get_image_vmaddr_slide(index);
|
||||
const char* name = _dyld_get_image_name(index);
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
memset(module, 0, sizeof(MDRawModule));
|
||||
|
||||
for (unsigned int i = 0; cmd && (i < header->ncmds); i++) {
|
||||
if (cmd->cmd == LC_SEGMENT) {
|
||||
if (cmd->cmd == LC_SEGMENT_ARCH) {
|
||||
|
||||
const breakpad_mach_segment_command *seg =
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
MDLocationDescriptor string_location;
|
||||
@@ -773,7 +1100,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||
return false;
|
||||
|
||||
module->base_of_image = seg->vmaddr + slide;
|
||||
module->size_of_image = seg->vmsize;
|
||||
module->size_of_image = static_cast<u_int32_t>(seg->vmsize);
|
||||
module->module_name_rva = string_location.rva;
|
||||
|
||||
if (!WriteCVRecord(module, cpu_type, name))
|
||||
@@ -910,7 +1237,7 @@ bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) {
|
||||
misc_info_stream->location = info.location();
|
||||
|
||||
MDRawMiscInfo *info_ptr = info.get();
|
||||
info_ptr->size_of_info = sizeof(MDRawMiscInfo);
|
||||
info_ptr->size_of_info = static_cast<u_int32_t>(sizeof(MDRawMiscInfo));
|
||||
info_ptr->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID |
|
||||
MD_MISCINFO_FLAGS1_PROCESS_TIMES |
|
||||
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO;
|
||||
@@ -922,33 +1249,39 @@ bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) {
|
||||
struct rusage usage;
|
||||
if (getrusage(RUSAGE_SELF, &usage) != -1) {
|
||||
// Omit the fractional time since the MDRawMiscInfo only wants seconds
|
||||
info_ptr->process_user_time = usage.ru_utime.tv_sec;
|
||||
info_ptr->process_kernel_time = usage.ru_stime.tv_sec;
|
||||
info_ptr->process_user_time =
|
||||
static_cast<u_int32_t>(usage.ru_utime.tv_sec);
|
||||
info_ptr->process_kernel_time =
|
||||
static_cast<u_int32_t>(usage.ru_stime.tv_sec);
|
||||
}
|
||||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, info_ptr->process_id };
|
||||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID,
|
||||
static_cast<int>(info_ptr->process_id) };
|
||||
u_int mibsize = static_cast<u_int>(sizeof(mib) / sizeof(mib[0]));
|
||||
size_t size;
|
||||
if (!sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &size, NULL, 0)) {
|
||||
if (!sysctl(mib, mibsize, NULL, &size, NULL, 0)) {
|
||||
mach_vm_address_t addr;
|
||||
if (mach_vm_allocate(mach_task_self(),
|
||||
&addr,
|
||||
size,
|
||||
true) == KERN_SUCCESS) {
|
||||
struct kinfo_proc *proc = (struct kinfo_proc *)addr;
|
||||
if (!sysctl(mib, sizeof(mib) / sizeof(mib[0]), proc, &size, NULL, 0))
|
||||
info_ptr->process_create_time = proc->kp_proc.p_starttime.tv_sec;
|
||||
if (!sysctl(mib, mibsize, proc, &size, NULL, 0))
|
||||
info_ptr->process_create_time =
|
||||
static_cast<u_int32_t>(proc->kp_proc.p_starttime.tv_sec);
|
||||
mach_vm_deallocate(mach_task_self(), addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
// Speed
|
||||
uint64_t speed;
|
||||
const uint64_t kOneMillion = 1000 * 1000;
|
||||
size = sizeof(speed);
|
||||
sysctlbyname("hw.cpufrequency_max", &speed, &size, NULL, 0);
|
||||
info_ptr->processor_max_mhz = speed / (1000 * 1000);
|
||||
info_ptr->processor_mhz_limit = speed / (1000 * 1000);
|
||||
info_ptr->processor_max_mhz = static_cast<u_int32_t>(speed / kOneMillion);
|
||||
info_ptr->processor_mhz_limit = static_cast<u_int32_t>(speed / kOneMillion);
|
||||
size = sizeof(speed);
|
||||
sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0);
|
||||
info_ptr->processor_current_mhz = speed / (1000 * 1000);
|
||||
info_ptr->processor_current_mhz = static_cast<u_int32_t>(speed / kOneMillion);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -37,38 +37,23 @@
|
||||
#include <string>
|
||||
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "common/memory.h"
|
||||
#include "common/mac/macho_utilities.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
#include "dynamic_images.h"
|
||||
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
|
||||
#define HAS_PPC_SUPPORT
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
|
||||
#if TARGET_CPU_X86_64 || TARGET_CPU_PPC64
|
||||
#define TOP_OF_THREAD0_STACK 0x00007fff5fbff000
|
||||
#else
|
||||
#define TOP_OF_THREAD0_STACK 0xbffff000
|
||||
#endif
|
||||
|
||||
#if TARGET_CPU_X86_64
|
||||
typedef x86_thread_state64_t breakpad_thread_state_t;
|
||||
typedef MDRawContextAMD64 MinidumpContext;
|
||||
#elif TARGET_CPU_X86
|
||||
typedef i386_thread_state_t breakpad_thread_state_t;
|
||||
typedef MDRawContextX86 MinidumpContext;
|
||||
#elif TARGET_CPU_PPC64
|
||||
typedef ppc_thread_state64_t breakpad_thread_state_t;
|
||||
typedef MDRawContextPPC64 MinidumpContext;
|
||||
#elif TARGET_CPU_PPC
|
||||
typedef ppc_thread_state_t breakpad_thread_state_t;
|
||||
typedef MDRawContextPPC MinidumpContext;
|
||||
#endif
|
||||
|
||||
// Use the REGISTER_FROM_THREADSTATE to access a register name from the
|
||||
// breakpad_thread_state_t structure.
|
||||
#if __DARWIN_UNIX03 || !TARGET_CPU_X86 || TARGET_CPU_X86_64
|
||||
#if __DARWIN_UNIX03 || TARGET_CPU_X86_64 || TARGET_CPU_PPC64
|
||||
// In The 10.5 SDK Headers Apple prepended __ to the variable names in the
|
||||
// i386_thread_state_t structure. There's no good way to tell what version of
|
||||
// the SDK we're compiling against so we just toggle on the same preprocessor
|
||||
@@ -119,6 +104,7 @@ class MinidumpGenerator {
|
||||
|
||||
// Stream writers
|
||||
bool WriteThreadListStream(MDRawDirectory *thread_list_stream);
|
||||
bool WriteMemoryListStream(MDRawDirectory *memory_list_stream);
|
||||
bool WriteExceptionStream(MDRawDirectory *exception_stream);
|
||||
bool WriteSystemInfoStream(MDRawDirectory *system_info_stream);
|
||||
bool WriteModuleListStream(MDRawDirectory *module_list_stream);
|
||||
@@ -127,6 +113,8 @@ class MinidumpGenerator {
|
||||
|
||||
// Helpers
|
||||
u_int64_t CurrentPCForStack(breakpad_thread_state_data_t state);
|
||||
bool GetThreadState(thread_act_t target_thread, thread_state_t state,
|
||||
mach_msg_type_number_t *count);
|
||||
bool WriteStackFromStartAddress(mach_vm_address_t start_addr,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteStack(breakpad_thread_state_data_t state,
|
||||
@@ -137,11 +125,33 @@ class MinidumpGenerator {
|
||||
bool WriteCVRecord(MDRawModule *module, int cpu_type,
|
||||
const char *module_path);
|
||||
bool WriteModuleStream(unsigned int index, MDRawModule *module);
|
||||
|
||||
size_t CalculateStackSize(mach_vm_address_t start_addr);
|
||||
|
||||
int FindExecutableModule();
|
||||
|
||||
// Per-CPU implementations of these methods
|
||||
#ifdef HAS_PPC_SUPPORT
|
||||
bool WriteStackPPC(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextPPC(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackPPC(breakpad_thread_state_data_t state);
|
||||
bool WriteStackPPC64(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextPPC64(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackPPC64(breakpad_thread_state_data_t state);
|
||||
#endif
|
||||
bool WriteStackX86(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextX86(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackX86(breakpad_thread_state_data_t state);
|
||||
bool WriteStackX86_64(breakpad_thread_state_data_t state,
|
||||
MDMemoryDescriptor *stack_location);
|
||||
bool WriteContextX86_64(breakpad_thread_state_data_t state,
|
||||
MDLocationDescriptor *register_location);
|
||||
u_int64_t CurrentPCForStackX86_64(breakpad_thread_state_data_t state);
|
||||
|
||||
// disallow copy ctor and operator=
|
||||
explicit MinidumpGenerator(const MinidumpGenerator &);
|
||||
void operator=(const MinidumpGenerator &);
|
||||
@@ -157,6 +167,9 @@ class MinidumpGenerator {
|
||||
mach_port_t crashing_task_;
|
||||
mach_port_t handler_thread_;
|
||||
|
||||
// CPU type of the task being dumped.
|
||||
cpu_type_t cpu_type_;
|
||||
|
||||
// System information
|
||||
static char build_string_[16];
|
||||
static int os_major_version_;
|
||||
@@ -165,6 +178,15 @@ class MinidumpGenerator {
|
||||
|
||||
// Information about dynamically loaded code
|
||||
DynamicImages *dynamic_images_;
|
||||
|
||||
// PageAllocator makes it possible to allocate memory
|
||||
// directly from the system, even while handling an exception.
|
||||
mutable PageAllocator allocator_;
|
||||
|
||||
// Blocks of memory written to the dump. These are all currently
|
||||
// written while writing the thread list stream, but saved here
|
||||
// so a memory list stream can be written afterwards.
|
||||
wasteful_vector<MDMemoryDescriptor> memory_blocks_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@@ -3,10 +3,24 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 42;
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8BFC813F11FF9A58002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; };
|
||||
8BFC814411FF9A9C002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; };
|
||||
8BFC814511FF9A9D002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; };
|
||||
8BFC814811FF9B13002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; };
|
||||
8BFC814911FF9B13002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; };
|
||||
8BFC814A11FF9B13002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; };
|
||||
8BFC814B11FF9B3F002CB4DC /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */; };
|
||||
8BFC814C11FF9B3F002CB4DC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */; };
|
||||
8BFC81A211FF9C2E002CB4DC /* CPlusTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC819211FF9C23002CB4DC /* CPlusTest.framework */; };
|
||||
8BFC81A311FF9C2F002CB4DC /* CPlusTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC819211FF9C23002CB4DC /* CPlusTest.framework */; };
|
||||
8BFC81AD11FF9C8A002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; };
|
||||
8BFC81AE11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; };
|
||||
8BFC81AF11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; };
|
||||
8BFC81B011FF9C8D002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; };
|
||||
9B35FF5A0B267D5F008DE8C7 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */; };
|
||||
9B35FF5B0B267D5F008DE8C7 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */; };
|
||||
9B37CEEC0AF98ECD00FA4BD4 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */; };
|
||||
@@ -94,12 +108,18 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
8BFC812011FF99D5002CB4DC /* Breakpad.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Breakpad.xcconfig; path = ../../../common/mac/Breakpad.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
8BFC812111FF99D5002CB4DC /* BreakpadDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadDebug.xcconfig; path = ../../../common/mac/BreakpadDebug.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
8BFC812211FF99D5002CB4DC /* BreakpadRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadRelease.xcconfig; path = ../../../common/mac/BreakpadRelease.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; };
|
||||
8BFC815411FF9B7F002CB4DC /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
|
||||
8BFC819211FF9C23002CB4DC /* CPlusTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CPlusTest.framework; path = Library/Frameworks/CPlusTest.framework; sourceTree = DEVELOPER_DIR; };
|
||||
8DD76F6C0486A84900D96B5E /* generator_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = generator_test; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9B35FF560B267D5F008DE8C7 /* convert_UTF.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = convert_UTF.c; path = ../../../common/convert_UTF.c; sourceTree = SOURCE_ROOT; };
|
||||
9B35FF570B267D5F008DE8C7 /* convert_UTF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = convert_UTF.h; path = ../../../common/convert_UTF.h; sourceTree = SOURCE_ROOT; };
|
||||
9B35FF580B267D5F008DE8C7 /* string_conversion.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = string_conversion.cc; path = ../../../common/string_conversion.cc; sourceTree = SOURCE_ROOT; };
|
||||
9B35FF590B267D5F008DE8C7 /* string_conversion.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = string_conversion.h; path = ../../../common/string_conversion.h; sourceTree = SOURCE_ROOT; };
|
||||
9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; };
|
||||
9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||
9B7CA84E0B1297F200CD3A1D /* unit_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unit_test; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_file_writer_unittest.cc; path = ../../minidump_file_writer_unittest.cc; sourceTree = "<group>"; };
|
||||
9BD82A9B0B00267E0055103E /* handler_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = handler_test; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -132,11 +152,11 @@
|
||||
F9721F310E8B07E800D7E813 /* dwarftests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = dwarftests.mm; sourceTree = "<group>"; };
|
||||
F9721F380E8B0CFC00D7E813 /* dump_syms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dump_syms.h; path = ../../../common/mac/dump_syms.h; sourceTree = SOURCE_ROOT; };
|
||||
F9721F390E8B0D0D00D7E813 /* dump_syms.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = dump_syms.mm; path = ../../../common/mac/dump_syms.mm; sourceTree = SOURCE_ROOT; };
|
||||
F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
||||
F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
F9721F760E8B0DC700D7E813 /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; };
|
||||
F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; };
|
||||
F9721F780E8B0DC700D7E813 /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; };
|
||||
F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = /System/Library/Frameworks/SenTestingKit.framework; sourceTree = "<absolute>"; };
|
||||
F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
F9721FA80E8B0E4800D7E813 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = md5.c; path = ../../../common/md5.c; sourceTree = SOURCE_ROOT; };
|
||||
F982089A0DB3280D0017AECA /* breakpad_nlist_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_test.h; sourceTree = "<group>"; };
|
||||
F982089B0DB3280D0017AECA /* breakpad_nlist_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = breakpad_nlist_test.cc; sourceTree = "<group>"; };
|
||||
@@ -156,6 +176,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9B37CEEC0AF98ECD00FA4BD4 /* CoreFoundation.framework in Frameworks */,
|
||||
8BFC813F11FF9A58002CB4DC /* libcrypto.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -163,6 +184,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8BFC814511FF9A9D002CB4DC /* libcrypto.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -171,6 +193,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9BD82AC10B0029DF0055103E /* CoreFoundation.framework in Frameworks */,
|
||||
8BFC814411FF9A9C002CB4DC /* libcrypto.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -178,6 +201,9 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8BFC814A11FF9B13002CB4DC /* libcrypto.dylib in Frameworks */,
|
||||
8BFC814B11FF9B3F002CB4DC /* SenTestingKit.framework in Frameworks */,
|
||||
8BFC814C11FF9B3F002CB4DC /* Cocoa.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -185,6 +211,8 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8BFC814811FF9B13002CB4DC /* libcrypto.dylib in Frameworks */,
|
||||
8BFC81A211FF9C2E002CB4DC /* CPlusTest.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -194,6 +222,8 @@
|
||||
files = (
|
||||
F9721F6C0E8B0D7000D7E813 /* Cocoa.framework in Frameworks */,
|
||||
F9721FA20E8B0E2300D7E813 /* SenTestingKit.framework in Frameworks */,
|
||||
8BFC814911FF9B13002CB4DC /* libcrypto.dylib in Frameworks */,
|
||||
8BFC81A311FF9C2F002CB4DC /* CPlusTest.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -203,6 +233,9 @@
|
||||
08FB7794FE84155DC02AAC07 /* MinidumpWriter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8BFC812011FF99D5002CB4DC /* Breakpad.xcconfig */,
|
||||
8BFC812111FF99D5002CB4DC /* BreakpadDebug.xcconfig */,
|
||||
8BFC812211FF99D5002CB4DC /* BreakpadRelease.xcconfig */,
|
||||
F9721FA80E8B0E4800D7E813 /* md5.c */,
|
||||
F9721F760E8B0DC700D7E813 /* bytereader.cc */,
|
||||
F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */,
|
||||
@@ -261,9 +294,12 @@
|
||||
9B37CEEA0AF98EB600FA4BD4 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */,
|
||||
8BFC815411FF9B7F002CB4DC /* Carbon.framework */,
|
||||
F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */,
|
||||
F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */,
|
||||
9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */,
|
||||
8BFC819211FF9C23002CB4DC /* CPlusTest.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -414,7 +450,7 @@
|
||||
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "minidump_test" */;
|
||||
compatibilityVersion = "Xcode 2.4";
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
hasScannedForEncodings = 1;
|
||||
mainGroup = 08FB7794FE84155DC02AAC07 /* MinidumpWriter */;
|
||||
projectDirPath = "";
|
||||
@@ -513,6 +549,7 @@
|
||||
D2F6510E0BEF94EB00920385 /* macho_walker.cc in Sources */,
|
||||
D2F651110BEF951700920385 /* string_conversion.cc in Sources */,
|
||||
D2F651150BEF953000920385 /* convert_UTF.c in Sources */,
|
||||
8BFC81B011FF9C8D002CB4DC /* breakpad_nlist_64.cc in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -524,6 +561,7 @@
|
||||
9B7CA8550B1298A100CD3A1D /* minidump_file_writer.cc in Sources */,
|
||||
9BC1D2940B336F2300F2A2B4 /* convert_UTF.c in Sources */,
|
||||
9BC1D2950B336F2500F2A2B4 /* string_conversion.cc in Sources */,
|
||||
8BFC81AE11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -543,6 +581,7 @@
|
||||
D2F6511E0BEF973600920385 /* macho_id.cc in Sources */,
|
||||
D2F6511F0BEF973900920385 /* macho_utilities.cc in Sources */,
|
||||
D2F651210BEF975400920385 /* macho_walker.cc in Sources */,
|
||||
8BFC81AF11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -580,6 +619,7 @@
|
||||
files = (
|
||||
F9AE5B390DBFDBDB00505983 /* dynamic_images.cc in Sources */,
|
||||
F9AE5B3A0DBFDBDB00505983 /* DynamicImagesTests.cc in Sources */,
|
||||
8BFC81AD11FF9C8A002CB4DC /* breakpad_nlist_64.cc in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -589,154 +629,78 @@
|
||||
1DEB923208733DC60010E9CD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_CW_ASM_SYNTAX = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||
GCC_ENABLE_PASCAL_STRINGS = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_THREADSAFE_STATICS = NO;
|
||||
INSTALL_PATH = "$(HOME)/bin";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(DEVELOPER_FRAMEWORKS_DIR)\"",
|
||||
);
|
||||
PRODUCT_NAME = generator_test;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1DEB923308733DC60010E9CD /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = (
|
||||
ppc,
|
||||
i386,
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(DEVELOPER_FRAMEWORKS_DIR)\"",
|
||||
);
|
||||
GCC_CW_ASM_SYNTAX = NO;
|
||||
GCC_ENABLE_PASCAL_STRINGS = NO;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_THREADSAFE_STATICS = NO;
|
||||
INSTALL_PATH = "$(HOME)/bin";
|
||||
PRODUCT_NAME = generator_test;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
1DEB923608733DC60010E9CD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 8BFC812111FF99D5002CB4DC /* BreakpadDebug.xcconfig */;
|
||||
buildSettings = {
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
OTHER_LDFLAGS = "-lcrypto";
|
||||
PREBINDING = NO;
|
||||
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1DEB923708733DC60010E9CD /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 8BFC812211FF99D5002CB4DC /* BreakpadRelease.xcconfig */;
|
||||
buildSettings = {
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
OTHER_LDFLAGS = "-lcrypto";
|
||||
PREBINDING = NO;
|
||||
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
9B7CA8510B12984300CD3A1D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
INSTALL_PATH = "$(HOME)/bin";
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = unit_test;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
9B7CA8520B12984300CD3A1D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
INSTALL_PATH = "$(HOME)/bin";
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = unit_test;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
9BD82AA70B0026BF0055103E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(NATIVE_ARCH)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
INSTALL_PATH = "$(HOME)/bin";
|
||||
OTHER_CFLAGS = "-Wall";
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = handler_test;
|
||||
USER_HEADER_SEARCH_PATHS = "../../.. $(inherited)";
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
9BD82AA80B0026BF0055103E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(NATIVE_ARCH)";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
INSTALL_PATH = "$(HOME)/bin";
|
||||
OTHER_CFLAGS = "-Wall";
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = handler_test;
|
||||
USER_HEADER_SEARCH_PATHS = "../../.. $(inherited)";
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F93A88770E8B4C700026AF89 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_CHAR_IS_UNSIGNED_CHAR = YES;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
INFOPLIST_FILE = "obj-cTestCases-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-lcrypto",
|
||||
"-framework",
|
||||
Cocoa,
|
||||
"-framework",
|
||||
SenTestingKit,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = octestcases;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../..//**";
|
||||
WRAPPER_EXTENSION = octest;
|
||||
@@ -746,58 +710,20 @@
|
||||
F93A88780E8B4C700026AF89 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_CHAR_IS_UNSIGNED_CHAR = YES;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
INFOPLIST_FILE = "obj-cTestCases-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-lcrypto",
|
||||
"-framework",
|
||||
Cocoa,
|
||||
"-framework",
|
||||
SenTestingKit,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = octestcases;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../..//**";
|
||||
WRAPPER_EXTENSION = octest;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F9AE19C40DB04A9500C98454 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = (
|
||||
"$(NATIVE_ARCH_64_BIT)",
|
||||
ppc64,
|
||||
);
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests64-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests64;
|
||||
SDKROOT = /Developer/SDKs/MacOSX10.5.sdk;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
};
|
||||
@@ -806,57 +732,19 @@
|
||||
F9AE19C50DB04A9500C98454 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = (
|
||||
"$(NATIVE_ARCH_64_BIT)",
|
||||
ppc64,
|
||||
);
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests64-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests64;
|
||||
SDKROOT = /Developer/SDKs/MacOSX10.5.sdk;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F9AE5B350DBFDBA300505983 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(NATIVE_ARCH)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests32-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
"-lcrypto",
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests32;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
@@ -866,28 +754,11 @@
|
||||
F9AE5B370DBFDBA300505983 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ARCHS = "$(NATIVE_ARCH)";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
|
||||
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Carbon.framework/Headers/Carbon.h";
|
||||
INFOPLIST_FILE = "minidump_tests32-Info.plist";
|
||||
INSTALL_PATH = "$(USER_LIBRARY_DIR)/Bundles";
|
||||
OTHER_LDFLAGS = (
|
||||
"-lcrypto",
|
||||
"-framework",
|
||||
Carbon,
|
||||
"-framework",
|
||||
CPlusTest,
|
||||
);
|
||||
PREBINDING = NO;
|
||||
PRODUCT_NAME = minidump_tests32;
|
||||
USER_HEADER_SEARCH_PATHS = "../../../**";
|
||||
WRAPPER_EXTENSION = cptest;
|
||||
ZERO_LINK = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@@ -59,7 +59,7 @@ ProtectedMemoryAllocator::~ProtectedMemoryAllocator() {
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
char *ProtectedMemoryAllocator::Allocate(size_t bytes) {
|
||||
char *ProtectedMemoryAllocator::Allocate(vm_size_t bytes) {
|
||||
if (valid_ && next_alloc_offset_ + bytes <= pool_size_) {
|
||||
char *p = (char*)base_address_ + next_alloc_offset_;
|
||||
next_alloc_offset_ += bytes;
|
||||
|
@@ -53,7 +53,7 @@ class ProtectedMemoryAllocator {
|
||||
// Fails by returning NULL is no more space is available.
|
||||
// Please note that the pointers returned from this method should not
|
||||
// be freed in any way (for example by calling free() on them ).
|
||||
char * Allocate(size_t n);
|
||||
char * Allocate(vm_size_t n);
|
||||
|
||||
// Returns the base address of the allocation pool.
|
||||
char * GetBaseAddress() { return (char*)base_address_; }
|
||||
@@ -78,7 +78,7 @@ class ProtectedMemoryAllocator {
|
||||
private:
|
||||
vm_size_t pool_size_;
|
||||
vm_address_t base_address_;
|
||||
int next_alloc_offset_;
|
||||
vm_size_t next_alloc_offset_;
|
||||
bool valid_;
|
||||
};
|
||||
|
||||
|
@@ -55,21 +55,17 @@ void DynamicImagesTests::ReadTaskMemoryTest() {
|
||||
// pick test2 as a symbol we know to be valid to read
|
||||
// anything will work, really
|
||||
void *addr = reinterpret_cast<void*>(&test2);
|
||||
void *buf;
|
||||
std::vector<uint8_t> buf(getpagesize());
|
||||
|
||||
fprintf(stderr, "reading 0x%p\n", addr);
|
||||
buf = google_breakpad::ReadTaskMemory(mach_task_self(),
|
||||
addr,
|
||||
getpagesize(),
|
||||
&kr);
|
||||
kr = google_breakpad::ReadTaskMemory(mach_task_self(),
|
||||
(uint64_t)addr,
|
||||
getpagesize(),
|
||||
buf);
|
||||
|
||||
CPTAssert(kr == KERN_SUCCESS);
|
||||
|
||||
CPTAssert(buf != NULL);
|
||||
|
||||
CPTAssert(0 == memcmp(buf, (const void*)addr, getpagesize()));
|
||||
|
||||
free(buf);
|
||||
CPTAssert(0 == memcmp(&buf[0], (const void*)addr, getpagesize()));
|
||||
}
|
||||
|
||||
void DynamicImagesTests::ReadLibrariesFromLocalTaskTest() {
|
||||
@@ -79,7 +75,5 @@ void DynamicImagesTests::ReadLibrariesFromLocalTaskTest() {
|
||||
|
||||
fprintf(stderr,"Local task image count: %d\n", d->GetImageCount());
|
||||
|
||||
d->TestPrint();
|
||||
|
||||
CPTAssert(d->GetImageCount() > 0);
|
||||
}
|
||||
|
69
src/client/mac/sender/Breakpad.nib/classes.nib
generated
69
src/client/mac/sender/Breakpad.nib/classes.nib
generated
@@ -1,69 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBClasses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CLASS</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSTextField</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ACTIONS</key>
|
||||
<dict>
|
||||
<key>cancel</key>
|
||||
<string>id</string>
|
||||
<key>sendReport</key>
|
||||
<string>id</string>
|
||||
<key>showPrivacyPolicy</key>
|
||||
<string>id</string>
|
||||
</dict>
|
||||
<key>CLASS</key>
|
||||
<string>Reporter</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>OUTLETS</key>
|
||||
<dict>
|
||||
<key>alertWindow_</key>
|
||||
<string>NSWindow</string>
|
||||
<key>cancelButton_</key>
|
||||
<string>NSButton</string>
|
||||
<key>commentMessage_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>commentsEntryField_</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>countdownLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>dialogTitle_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailEntryField_</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>emailLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailMessage_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailSectionBox_</key>
|
||||
<string>NSBox</string>
|
||||
<key>headerBox_</key>
|
||||
<string>NSBox</string>
|
||||
<key>preEmailBox_</key>
|
||||
<string>NSBox</string>
|
||||
<key>privacyLinkArrow_</key>
|
||||
<string>NSView</string>
|
||||
<key>privacyLinkLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>sendButton_</key>
|
||||
<string>NSButton</string>
|
||||
</dict>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>IBVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
20
src/client/mac/sender/Breakpad.nib/info.nib
generated
20
src/client/mac/sender/Breakpad.nib/info.nib
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBFramework Version</key>
|
||||
<string>676</string>
|
||||
<key>IBLastKnownRelativeProjectPath</key>
|
||||
<string>../Breakpad.xcodeproj</string>
|
||||
<key>IBOldestOS</key>
|
||||
<integer>5</integer>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>132</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>9J61</string>
|
||||
<key>targetFramework</key>
|
||||
<string>IBCocoaFramework</string>
|
||||
</dict>
|
||||
</plist>
|
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
Binary file not shown.
1140
src/client/mac/sender/Breakpad.xib
Normal file
1140
src/client/mac/sender/Breakpad.xib
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -35,6 +35,7 @@
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include "client/mac/Framework/Breakpad.h"
|
||||
#import "GTMDefines.h"
|
||||
|
||||
#define kClientIdPreferenceKey @"clientid"
|
||||
|
||||
@@ -53,10 +54,10 @@ extern NSString *const kDefaultServerType;
|
||||
// work in the middle of a validation.
|
||||
@interface LengthLimitingTextField : NSTextField {
|
||||
@private
|
||||
unsigned int maximumLength_;
|
||||
NSUInteger maximumLength_;
|
||||
}
|
||||
|
||||
- (void) setMaximumLength:(unsigned int)maxLength;
|
||||
- (void)setMaximumLength:(NSUInteger)maxLength;
|
||||
@end
|
||||
|
||||
@interface Reporter : NSObject {
|
||||
|
@@ -56,20 +56,20 @@ NSString *const kDefaultServerType = @"google";
|
||||
|
||||
@interface NSView (ResizabilityExtentions)
|
||||
// Shifts the view vertically by the given amount.
|
||||
- (void)breakpad_shiftVertically:(float)offset;
|
||||
- (void)breakpad_shiftVertically:(CGFloat)offset;
|
||||
|
||||
// Shifts the view horizontally by the given amount.
|
||||
- (void)breakpad_shiftHorizontally:(float)offset;
|
||||
- (void)breakpad_shiftHorizontally:(CGFloat)offset;
|
||||
@end
|
||||
|
||||
@implementation NSView (ResizabilityExtentions)
|
||||
- (void)breakpad_shiftVertically:(float)offset {
|
||||
- (void)breakpad_shiftVertically:(CGFloat)offset {
|
||||
NSPoint origin = [self frame].origin;
|
||||
origin.y += offset;
|
||||
[self setFrameOrigin:origin];
|
||||
}
|
||||
|
||||
- (void)breakpad_shiftHorizontally:(float)offset {
|
||||
- (void)breakpad_shiftHorizontally:(CGFloat)offset {
|
||||
NSPoint origin = [self frame].origin;
|
||||
origin.x += offset;
|
||||
[self setFrameOrigin:origin];
|
||||
@@ -79,11 +79,11 @@ NSString *const kDefaultServerType = @"google";
|
||||
@interface NSWindow (ResizabilityExtentions)
|
||||
// Adjusts the window height by heightDelta relative to its current height,
|
||||
// keeping all the content at the same size.
|
||||
- (void)breakpad_adjustHeight:(float)heightDelta;
|
||||
- (void)breakpad_adjustHeight:(CGFloat)heightDelta;
|
||||
@end
|
||||
|
||||
@implementation NSWindow (ResizabilityExtentions)
|
||||
- (void)breakpad_adjustHeight:(float)heightDelta {
|
||||
- (void)breakpad_adjustHeight:(CGFloat)heightDelta {
|
||||
[[self contentView] setAutoresizesSubviews:NO];
|
||||
|
||||
NSRect windowFrame = [self frame];
|
||||
@@ -101,16 +101,16 @@ NSString *const kDefaultServerType = @"google";
|
||||
// Grows or shrinks the height of the field to the minimum required to show the
|
||||
// current text, preserving the existing width and origin.
|
||||
// Returns the change in height.
|
||||
- (float)breakpad_adjustHeightToFit;
|
||||
- (CGFloat)breakpad_adjustHeightToFit;
|
||||
|
||||
// Grows or shrinks the width of the field to the minimum required to show the
|
||||
// current text, preserving the existing height and origin.
|
||||
// Returns the change in width.
|
||||
- (float)breakpad_adjustWidthToFit;
|
||||
- (CGFloat)breakpad_adjustWidthToFit;
|
||||
@end
|
||||
|
||||
@implementation NSTextField (ResizabilityExtentions)
|
||||
- (float)breakpad_adjustHeightToFit {
|
||||
- (CGFloat)breakpad_adjustHeightToFit {
|
||||
NSRect oldFrame = [self frame];
|
||||
// Starting with the 10.5 SDK, height won't grow, so make it huge to start.
|
||||
NSRect presizeFrame = oldFrame;
|
||||
@@ -125,7 +125,7 @@ NSString *const kDefaultServerType = @"google";
|
||||
return newSize.height - NSHeight(oldFrame);
|
||||
}
|
||||
|
||||
- (float)breakpad_adjustWidthToFit {
|
||||
- (CGFloat)breakpad_adjustWidthToFit {
|
||||
NSRect oldFrame = [self frame];
|
||||
[self sizeToFit];
|
||||
return NSWidth([self frame]) - NSWidth(oldFrame);
|
||||
@@ -136,11 +136,11 @@ NSString *const kDefaultServerType = @"google";
|
||||
// Resizes to fit the label using IB-style size-to-fit metrics and enforcing a
|
||||
// minimum width of 70, while preserving the right edge location.
|
||||
// Returns the change in width.
|
||||
- (float)breakpad_smartSizeToFit;
|
||||
- (CGFloat)breakpad_smartSizeToFit;
|
||||
@end
|
||||
|
||||
@implementation NSButton (ResizabilityExtentions)
|
||||
- (float)breakpad_smartSizeToFit {
|
||||
- (CGFloat)breakpad_smartSizeToFit {
|
||||
NSRect oldFrame = [self frame];
|
||||
[self sizeToFit];
|
||||
NSRect newFrame = [self frame];
|
||||
@@ -218,7 +218,8 @@ NSString *const kDefaultServerType = @"google";
|
||||
// Run an alert window with the given timeout. Returns
|
||||
// NSRunStoppedResponse if the timeout is exceeded. A timeout of 0
|
||||
// queues the message immediately in the modal run loop.
|
||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout;
|
||||
- (NSInteger)runModalWindow:(NSWindow*)window
|
||||
withTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
// Returns a unique client id (user-specific), creating a persistent
|
||||
// one in the user defaults, if necessary.
|
||||
@@ -254,6 +255,9 @@ NSString *const kDefaultServerType = @"google";
|
||||
// in their comments/email.
|
||||
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
|
||||
|
||||
// Records the uploaded crash ID to the log file.
|
||||
- (void)logUploadWithID:(const char *)uploadID;
|
||||
|
||||
@end
|
||||
|
||||
@implementation Reporter
|
||||
@@ -386,7 +390,7 @@ NSString *const kDefaultServerType = @"google";
|
||||
}
|
||||
|
||||
// Otherwise, if we have no client id, generate one!
|
||||
srandom([[NSDate date] timeIntervalSince1970]);
|
||||
srandom((int)[[NSDate date] timeIntervalSince1970]);
|
||||
long clientId1 = random();
|
||||
long clientId2 = random();
|
||||
long clientId3 = random();
|
||||
@@ -403,8 +407,8 @@ NSString *const kDefaultServerType = @"google";
|
||||
unsigned int logFileCounter = 0;
|
||||
|
||||
NSString *logPath;
|
||||
int logFileTailSize = [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE]
|
||||
intValue];
|
||||
size_t logFileTailSize =
|
||||
[[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
|
||||
|
||||
NSMutableArray *logFilenames; // An array of NSString, one per log file
|
||||
logFilenames = [[NSMutableArray alloc] init];
|
||||
@@ -544,7 +548,7 @@ NSString *const kDefaultServerType = @"google";
|
||||
// Get the timeout value for the notification.
|
||||
NSTimeInterval timeout = [self messageTimeout];
|
||||
|
||||
int buttonPressed = NSAlertAlternateReturn;
|
||||
NSInteger buttonPressed = NSAlertAlternateReturn;
|
||||
// Determine whether we should create a text box for user feedback.
|
||||
if ([self shouldRequestComments]) {
|
||||
BOOL didLoadNib = [NSBundle loadNibNamed:@"Breakpad" owner:self];
|
||||
@@ -592,7 +596,7 @@ NSString *const kDefaultServerType = @"google";
|
||||
[commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@",
|
||||
[self explanatoryDialogText],
|
||||
NSLocalizedString(@"commentsMsg", @"")]];
|
||||
float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
|
||||
CGFloat commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
|
||||
[headerBox_ breakpad_shiftVertically:commentHeightDelta];
|
||||
[alertWindow_ breakpad_adjustHeight:commentHeightDelta];
|
||||
|
||||
@@ -600,7 +604,7 @@ NSString *const kDefaultServerType = @"google";
|
||||
// section depending on whether or not we are asking for email.
|
||||
if (includeEmail) {
|
||||
[emailMessage_ setStringValue:NSLocalizedString(@"emailMsg", @"")];
|
||||
float emailHeightDelta = [emailMessage_ breakpad_adjustHeightToFit];
|
||||
CGFloat emailHeightDelta = [emailMessage_ breakpad_adjustHeightToFit];
|
||||
[preEmailBox_ breakpad_shiftVertically:emailHeightDelta];
|
||||
[alertWindow_ breakpad_adjustHeight:emailHeightDelta];
|
||||
} else {
|
||||
@@ -609,17 +613,36 @@ NSString *const kDefaultServerType = @"google";
|
||||
|
||||
// Localize the email label, and shift the associated text field.
|
||||
[emailLabel_ setStringValue:NSLocalizedString(@"emailLabel", @"")];
|
||||
float emailLabelWidthDelta = [emailLabel_ breakpad_adjustWidthToFit];
|
||||
CGFloat emailLabelWidthDelta = [emailLabel_ breakpad_adjustWidthToFit];
|
||||
[emailEntryField_ breakpad_shiftHorizontally:emailLabelWidthDelta];
|
||||
|
||||
// Localize the privacy policy label, and keep it right-aligned to the arrow.
|
||||
[privacyLinkLabel_ setStringValue:NSLocalizedString(@"privacyLabel", @"")];
|
||||
float privacyLabelWidthDelta = [privacyLinkLabel_ breakpad_adjustWidthToFit];
|
||||
CGFloat privacyLabelWidthDelta =
|
||||
[privacyLinkLabel_ breakpad_adjustWidthToFit];
|
||||
[privacyLinkLabel_ breakpad_shiftHorizontally:(-privacyLabelWidthDelta)];
|
||||
|
||||
// Ensure that the email field and the privacy policy link don't overlap.
|
||||
CGFloat kMinControlPadding = 8;
|
||||
CGFloat maxEmailFieldWidth = NSMinX([privacyLinkLabel_ frame]) -
|
||||
NSMinX([emailEntryField_ frame]) -
|
||||
kMinControlPadding;
|
||||
if (NSWidth([emailEntryField_ bounds]) > maxEmailFieldWidth &&
|
||||
maxEmailFieldWidth > 0) {
|
||||
NSSize emailSize = [emailEntryField_ frame].size;
|
||||
emailSize.width = maxEmailFieldWidth;
|
||||
[emailEntryField_ setFrameSize:emailSize];
|
||||
}
|
||||
|
||||
// Localize the placeholder text.
|
||||
[[commentsEntryField_ cell]
|
||||
setPlaceholderString:NSLocalizedString(@"commentsPlaceholder", @"")];
|
||||
[[emailEntryField_ cell]
|
||||
setPlaceholderString:NSLocalizedString(@"emailPlaceholder", @"")];
|
||||
|
||||
// Localize the buttons, and keep the cancel button at the right distance.
|
||||
[sendButton_ setTitle:NSLocalizedString(@"sendReportButton", @"")];
|
||||
float sendButtonWidthDelta = [sendButton_ breakpad_smartSizeToFit];
|
||||
CGFloat sendButtonWidthDelta = [sendButton_ breakpad_smartSizeToFit];
|
||||
[cancelButton_ breakpad_shiftHorizontally:(-sendButtonWidthDelta)];
|
||||
[cancelButton_ setTitle:NSLocalizedString(@"cancelButton", @"")];
|
||||
[cancelButton_ breakpad_smartSizeToFit];
|
||||
@@ -627,12 +650,13 @@ NSString *const kDefaultServerType = @"google";
|
||||
|
||||
- (void)removeEmailPrompt {
|
||||
[emailSectionBox_ setHidden:YES];
|
||||
float emailSectionHeight = NSHeight([emailSectionBox_ frame]);
|
||||
CGFloat emailSectionHeight = NSHeight([emailSectionBox_ frame]);
|
||||
[preEmailBox_ breakpad_shiftVertically:(-emailSectionHeight)];
|
||||
[alertWindow_ breakpad_adjustHeight:(-emailSectionHeight)];
|
||||
}
|
||||
|
||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout {
|
||||
- (NSInteger)runModalWindow:(NSWindow*)window
|
||||
withTimeout:(NSTimeInterval)timeout {
|
||||
// Queue a |stopModal| message to be performed in |timeout| seconds.
|
||||
if (timeout > 0.001) {
|
||||
remainingDialogTime_ = timeout;
|
||||
@@ -647,7 +671,7 @@ NSString *const kDefaultServerType = @"google";
|
||||
// Run the window modally and wait for either a |stopModal| message or a
|
||||
// button click.
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
int returnMethod = [NSApp runModalForWindow:window];
|
||||
NSInteger returnMethod = [NSApp runModalForWindow:window];
|
||||
|
||||
return returnMethod;
|
||||
}
|
||||
@@ -711,7 +735,7 @@ doCommandBySelector:(SEL)commandSelector {
|
||||
|
||||
if (remainingDialogTime_ > 59) {
|
||||
// calculate minutes remaining for UI purposes
|
||||
displayedTimeLeft = (remainingDialogTime_ / 60);
|
||||
displayedTimeLeft = (int)(remainingDialogTime_ / 60);
|
||||
|
||||
if (displayedTimeLeft == 1) {
|
||||
formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @"");
|
||||
@@ -719,8 +743,8 @@ doCommandBySelector:(SEL)commandSelector {
|
||||
formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @"");
|
||||
}
|
||||
} else {
|
||||
displayedTimeLeft = remainingDialogTime_;
|
||||
if (remainingDialogTime_ == 1) {
|
||||
displayedTimeLeft = (int)remainingDialogTime_;
|
||||
if (displayedTimeLeft == 1) {
|
||||
formatString = NSLocalizedString(@"countdownMsgSecondSingular", @"");
|
||||
} else {
|
||||
formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @"");
|
||||
@@ -791,7 +815,8 @@ doCommandBySelector:(SEL)commandSelector {
|
||||
NSTimeInterval now = CFAbsoluteTimeGetCurrent();
|
||||
NSTimeInterval spanSeconds = (now - lastTime);
|
||||
|
||||
[programDict setObject:[NSNumber numberWithFloat:now] forKey:kLastSubmission];
|
||||
[programDict setObject:[NSNumber numberWithDouble:now]
|
||||
forKey:kLastSubmission];
|
||||
[ud setObject:programDict forKey:program];
|
||||
[ud synchronize];
|
||||
|
||||
@@ -896,8 +921,8 @@ doCommandBySelector:(SEL)commandSelector {
|
||||
forKey:@BREAKPAD_VERSION];
|
||||
[socorroDictionary_ setObject:@"ProductName"
|
||||
forKey:@BREAKPAD_PRODUCT];
|
||||
[socorroDictionary_ setObject:@"ProductName"
|
||||
forKey:@BREAKPAD_PRODUCT];
|
||||
[socorroDictionary_ setObject:@"Email"
|
||||
forKey:@BREAKPAD_EMAIL];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
|
||||
@@ -983,6 +1008,7 @@ doCommandBySelector:(SEL)commandSelector {
|
||||
} else {
|
||||
NSCharacterSet *trimSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
||||
reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
|
||||
[self logUploadWithID:reportID];
|
||||
}
|
||||
|
||||
// rename the minidump file according to the id returned from the server
|
||||
@@ -1027,6 +1053,29 @@ doCommandBySelector:(SEL)commandSelector {
|
||||
[upload release];
|
||||
}
|
||||
|
||||
- (void)logUploadWithID:(const char *)uploadID {
|
||||
NSString *minidumpDir =
|
||||
[parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
||||
NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
|
||||
minidumpDir, kReporterLogFilename];
|
||||
NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
|
||||
[[NSDate date] timeIntervalSince1970], uploadID];
|
||||
NSData *logData = [logLine dataUsingEncoding:kCFStringEncodingUTF8];
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
if ([fileManager fileExistsAtPath:logFilePath]) {
|
||||
NSFileHandle *logFileHandle =
|
||||
[NSFileHandle fileHandleForWritingAtPath:logFilePath];
|
||||
[logFileHandle seekToEndOfFile];
|
||||
[logFileHandle writeData:logData];
|
||||
[logFileHandle closeFile];
|
||||
} else {
|
||||
[fileManager createFileAtPath:logFilePath
|
||||
contents:logData
|
||||
attributes:nil];
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (void)dealloc {
|
||||
[parameters_ release];
|
||||
@@ -1049,7 +1098,7 @@ doCommandBySelector:(SEL)commandSelector {
|
||||
//=============================================================================
|
||||
@implementation LengthLimitingTextField
|
||||
|
||||
- (void) setMaximumLength:(unsigned int)maxLength {
|
||||
- (void)setMaximumLength:(NSUInteger)maxLength {
|
||||
maximumLength_ = maxLength;
|
||||
}
|
||||
|
||||
@@ -1066,7 +1115,7 @@ shouldChangeTextInRange:(NSRange)affectedCharRange
|
||||
}
|
||||
// Figure out what the new string length would be, taking into
|
||||
// account user selections.
|
||||
int newStringLength =
|
||||
NSUInteger newStringLength =
|
||||
[[textView string] length] - affectedCharRange.length +
|
||||
[replacementString length];
|
||||
if (newStringLength > maximumLength_) {
|
||||
@@ -1082,7 +1131,7 @@ shouldChangeTextInRange:(NSRange)affectedCharRange
|
||||
NSText* fieldEditor = [self currentEditor];
|
||||
if (fieldEditor != nil) {
|
||||
// Check for a single "Command" modifier
|
||||
unsigned int modifiers = [event modifierFlags];
|
||||
NSUInteger modifiers = [event modifierFlags];
|
||||
modifiers &= NSDeviceIndependentModifierFlagsMask;
|
||||
if (modifiers == NSCommandKeyMask) {
|
||||
// Now, check for Select All, Cut, Copy, or Paste key equivalents.
|
||||
|
BIN
src/client/mac/sender/da.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/da.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/da.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/da.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/de.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/de.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/de.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/de.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/es.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/es.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/es.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/es.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/fr.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/fr.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/fr.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/fr.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/it.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/it.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/it.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/it.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/ja.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/ja.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/ja.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/ja.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/nl.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/nl.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/nl.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/nl.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/no.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/no.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/no.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/no.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/sl.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/sl.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/sl.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/sl.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/sv.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/sv.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/sv.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/sv.lproj/Localizable.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/tr.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/sender/tr.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
src/client/mac/sender/tr.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/tr.lproj/Localizable.strings
Normal file
Binary file not shown.
@@ -31,6 +31,7 @@
|
||||
|
||||
#import "Controller.h"
|
||||
#import "TestClass.h"
|
||||
#import "GTMDefines.h"
|
||||
#include <unistd.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
@@ -51,7 +52,7 @@
|
||||
}
|
||||
|
||||
- (IBAction)forkTestOptions:(id)sender {
|
||||
int tag = [[sender selectedCell] tag];
|
||||
NSInteger tag = [[sender selectedCell] tag];
|
||||
NSLog(@"sender tag: %d", tag);
|
||||
if (tag <= 2) {
|
||||
bpForkOption = tag;
|
||||
@@ -75,7 +76,7 @@
|
||||
|
||||
NSString *resourcePath = [[NSBundle bundleForClass:
|
||||
[self class]] resourcePath];
|
||||
NSString *execProgname;
|
||||
NSString *execProgname = nil;
|
||||
if (progCrashPoint == DURINGLAUNCH) {
|
||||
execProgname = [resourcePath stringByAppendingString:@"/crashduringload"];
|
||||
} else if (progCrashPoint == AFTERLAUNCH) {
|
||||
@@ -129,11 +130,11 @@
|
||||
}
|
||||
|
||||
- (IBAction)crash:(id)sender {
|
||||
int tag = [sender tag];
|
||||
NSInteger tag = [sender tag];
|
||||
|
||||
if (tag == 1) {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self];
|
||||
[self performSelector:@selector(causeCrash) withObject:nil afterDelay:10];
|
||||
[self performSelector:@selector(causeCrash) withObject:nil afterDelay:10.0];
|
||||
[sender setState:NSOnState];
|
||||
return;
|
||||
}
|
||||
|
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBClasses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ACTIONS</key>
|
||||
<dict>
|
||||
<key>crash</key>
|
||||
<string>id</string>
|
||||
<key>forkTestGo</key>
|
||||
<string>id</string>
|
||||
<key>forkTestOptions</key>
|
||||
<string>id</string>
|
||||
<key>generateReportWithoutCrash</key>
|
||||
<string>id</string>
|
||||
<key>showForkTestWindow</key>
|
||||
<string>id</string>
|
||||
</dict>
|
||||
<key>CLASS</key>
|
||||
<string>Controller</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>OUTLETS</key>
|
||||
<dict>
|
||||
<key>forkTestOptions_</key>
|
||||
<string>NSWindow</string>
|
||||
<key>window_</key>
|
||||
<string>NSWindow</string>
|
||||
</dict>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CLASS</key>
|
||||
<string>FirstResponder</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>IBVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBFramework Version</key>
|
||||
<string>670</string>
|
||||
<key>IBLastKnownRelativeProjectPath</key>
|
||||
<string>../GoogleBreakpadTest.xcodeproj</string>
|
||||
<key>IBOldestOS</key>
|
||||
<integer>5</integer>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>221</integer>
|
||||
<integer>29</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>9F33</string>
|
||||
<key>targetFramework</key>
|
||||
<string>IBCocoaFramework</string>
|
||||
</dict>
|
||||
</plist>
|
Binary file not shown.
3748
src/client/mac/testapp/English.lproj/MainMenu.xib
Normal file
3748
src/client/mac/testapp/English.lproj/MainMenu.xib
Normal file
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,7 @@ void InternalTestClass::InternalFunction(AStruct &s) {
|
||||
float InternalTestClass::kStaticFloatValue = 42;
|
||||
|
||||
static float PlainOldFunction() {
|
||||
return 3.14145;
|
||||
return 3.14145f;
|
||||
}
|
||||
|
||||
@implementation TestClass
|
||||
|
@@ -27,7 +27,7 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <GTMSenTestCase.h>
|
||||
#import "GTMSenTestCase.h"
|
||||
#import "SimpleStringDictionary.h"
|
||||
|
||||
@interface SimpleStringDictionaryTest : GTMTestCase {
|
||||
|
72
src/client/mac/tests/auto_tempdir.h
Normal file
72
src/client/mac/tests/auto_tempdir.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2010, 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.
|
||||
|
||||
// Utility class for creating a temporary directory for unit tests
|
||||
// that is deleted in the destructor.
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_AUTO_TEMPDIR
|
||||
#define GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_AUTO_TEMPDIR
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class AutoTempDir {
|
||||
public:
|
||||
AutoTempDir() {
|
||||
char tempDir[16] = "/tmp/XXXXXXXXXX";
|
||||
mkdtemp(tempDir);
|
||||
path = tempDir;
|
||||
}
|
||||
|
||||
~AutoTempDir() {
|
||||
// First remove any files in the dir
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (!dir)
|
||||
return;
|
||||
|
||||
dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
continue;
|
||||
std::string entryPath = path + "/" + entry->d_name;
|
||||
unlink(entryPath.c_str());
|
||||
}
|
||||
closedir(dir);
|
||||
rmdir(path.c_str());
|
||||
}
|
||||
|
||||
std::string path;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_AUTO_TEMPDIR
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user