mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-11-24 03:15:41 +01:00
Add some unit tests for Linux WriteSymbolFile
This patch adds synth_elf::{StringTable,SymbolTable,ELF} classes to produce in-memory ELF files to properly test the Linux symbol dumping code. It also uses those classes to add some basic tests for the WriteSymbolFile function. R=jimb at http://breakpad.appspot.com/277001/show git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@794 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
b2f96f314c
commit
3ca4a120de
@ -381,8 +381,11 @@ src_common_dumper_unittest_SOURCES = \
|
||||
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 \
|
||||
|
66
Makefile.in
66
Makefile.in
@ -451,8 +451,11 @@ am__src_common_dumper_unittest_SOURCES_DIST = \
|
||||
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 \
|
||||
@ -481,8 +484,11 @@ am__src_common_dumper_unittest_SOURCES_DIST = \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/dwarf/src_common_dumper_unittest-dwarf2reader.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/dwarf/src_common_dumper_unittest-dwarf2reader_cfi_unittest.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-dump_symbols.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-elf_symbols_to_module.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-elf_symbols_to_module_unittest.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-synth_elf.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-synth_elf_unittest.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-file_id.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/src_common_dumper_unittest-file_id_unittest.$(OBJEXT) \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/testing/gtest/src/src_common_dumper_unittest-gtest-all.$(OBJEXT) \
|
||||
@ -1497,8 +1503,11 @@ TESTS_ENVIRONMENT =
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/dwarf/dwarf2reader.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/dwarf/dwarf2reader_cfi_unittest.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/dump_symbols.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/dump_symbols_unittest.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/elf_symbols_to_module.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/elf_symbols_to_module_unittest.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/synth_elf.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/synth_elf_unittest.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/file_id.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/common/linux/file_id_unittest.cc \
|
||||
@DISABLE_TOOLS_FALSE@@LINUX_HOST_TRUE@ src/testing/gtest/src/gtest-all.cc \
|
||||
@ -2662,12 +2671,21 @@ src/common/dwarf/src_common_dumper_unittest-dwarf2reader_cfi_unittest.$(OBJEXT):
|
||||
src/common/linux/src_common_dumper_unittest-dump_symbols.$(OBJEXT): \
|
||||
src/common/linux/$(am__dirstamp) \
|
||||
src/common/linux/$(DEPDIR)/$(am__dirstamp)
|
||||
src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.$(OBJEXT): \
|
||||
src/common/linux/$(am__dirstamp) \
|
||||
src/common/linux/$(DEPDIR)/$(am__dirstamp)
|
||||
src/common/linux/src_common_dumper_unittest-elf_symbols_to_module.$(OBJEXT): \
|
||||
src/common/linux/$(am__dirstamp) \
|
||||
src/common/linux/$(DEPDIR)/$(am__dirstamp)
|
||||
src/common/linux/src_common_dumper_unittest-elf_symbols_to_module_unittest.$(OBJEXT): \
|
||||
src/common/linux/$(am__dirstamp) \
|
||||
src/common/linux/$(DEPDIR)/$(am__dirstamp)
|
||||
src/common/linux/src_common_dumper_unittest-synth_elf.$(OBJEXT): \
|
||||
src/common/linux/$(am__dirstamp) \
|
||||
src/common/linux/$(DEPDIR)/$(am__dirstamp)
|
||||
src/common/linux/src_common_dumper_unittest-synth_elf_unittest.$(OBJEXT): \
|
||||
src/common/linux/$(am__dirstamp) \
|
||||
src/common/linux/$(DEPDIR)/$(am__dirstamp)
|
||||
src/common/linux/src_common_dumper_unittest-file_id.$(OBJEXT): \
|
||||
src/common/linux/$(am__dirstamp) \
|
||||
src/common/linux/$(DEPDIR)/$(am__dirstamp)
|
||||
@ -3150,10 +3168,13 @@ mostlyclean-compile:
|
||||
-rm -f src/common/linux/guid_creator.$(OBJEXT)
|
||||
-rm -f src/common/linux/http_upload.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-dump_symbols.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-elf_symbols_to_module.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-elf_symbols_to_module_unittest.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-file_id.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-file_id_unittest.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-synth_elf.$(OBJEXT)
|
||||
-rm -f src/common/linux/src_common_dumper_unittest-synth_elf_unittest.$(OBJEXT)
|
||||
-rm -f src/common/md5.$(OBJEXT)
|
||||
-rm -f src/common/module.$(OBJEXT)
|
||||
-rm -f src/common/src_client_linux_linux_client_unittest-memory_unittest.$(OBJEXT)
|
||||
@ -3384,10 +3405,13 @@ distclean-compile:
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/guid_creator.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/http_upload.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-elf_symbols_to_module.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-elf_symbols_to_module_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-file_id.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-file_id_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/address_map_unittest.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/basic_code_modules.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/basic_source_line_resolver.Po@am__quote@
|
||||
@ -4077,6 +4101,20 @@ src/common/linux/src_common_dumper_unittest-dump_symbols.obj: src/common/linux/d
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-dump_symbols.obj `if test -f 'src/common/linux/dump_symbols.cc'; then $(CYGPATH_W) 'src/common/linux/dump_symbols.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/dump_symbols.cc'; fi`
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.o: src/common/linux/dump_symbols_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.o -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols_unittest.Tpo -c -o src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.o `test -f 'src/common/linux/dump_symbols_unittest.cc' || echo '$(srcdir)/'`src/common/linux/dump_symbols_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols_unittest.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols_unittest.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/dump_symbols_unittest.cc' object='src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.o' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.o `test -f 'src/common/linux/dump_symbols_unittest.cc' || echo '$(srcdir)/'`src/common/linux/dump_symbols_unittest.cc
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.obj: src/common/linux/dump_symbols_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.obj -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols_unittest.Tpo -c -o src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.obj `if test -f 'src/common/linux/dump_symbols_unittest.cc'; then $(CYGPATH_W) 'src/common/linux/dump_symbols_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/dump_symbols_unittest.cc'; fi`
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols_unittest.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-dump_symbols_unittest.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/dump_symbols_unittest.cc' object='src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.obj' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-dump_symbols_unittest.obj `if test -f 'src/common/linux/dump_symbols_unittest.cc'; then $(CYGPATH_W) 'src/common/linux/dump_symbols_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/dump_symbols_unittest.cc'; fi`
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-elf_symbols_to_module.o: src/common/linux/elf_symbols_to_module.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-elf_symbols_to_module.o -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-elf_symbols_to_module.Tpo -c -o src/common/linux/src_common_dumper_unittest-elf_symbols_to_module.o `test -f 'src/common/linux/elf_symbols_to_module.cc' || echo '$(srcdir)/'`src/common/linux/elf_symbols_to_module.cc
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-elf_symbols_to_module.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-elf_symbols_to_module.Po
|
||||
@ -4105,6 +4143,34 @@ src/common/linux/src_common_dumper_unittest-elf_symbols_to_module_unittest.obj:
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-elf_symbols_to_module_unittest.obj `if test -f 'src/common/linux/elf_symbols_to_module_unittest.cc'; then $(CYGPATH_W) 'src/common/linux/elf_symbols_to_module_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/elf_symbols_to_module_unittest.cc'; fi`
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-synth_elf.o: src/common/linux/synth_elf.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-synth_elf.o -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Tpo -c -o src/common/linux/src_common_dumper_unittest-synth_elf.o `test -f 'src/common/linux/synth_elf.cc' || echo '$(srcdir)/'`src/common/linux/synth_elf.cc
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/synth_elf.cc' object='src/common/linux/src_common_dumper_unittest-synth_elf.o' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-synth_elf.o `test -f 'src/common/linux/synth_elf.cc' || echo '$(srcdir)/'`src/common/linux/synth_elf.cc
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-synth_elf.obj: src/common/linux/synth_elf.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-synth_elf.obj -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Tpo -c -o src/common/linux/src_common_dumper_unittest-synth_elf.obj `if test -f 'src/common/linux/synth_elf.cc'; then $(CYGPATH_W) 'src/common/linux/synth_elf.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/synth_elf.cc'; fi`
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/synth_elf.cc' object='src/common/linux/src_common_dumper_unittest-synth_elf.obj' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-synth_elf.obj `if test -f 'src/common/linux/synth_elf.cc'; then $(CYGPATH_W) 'src/common/linux/synth_elf.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/synth_elf.cc'; fi`
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-synth_elf_unittest.o: src/common/linux/synth_elf_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-synth_elf_unittest.o -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Tpo -c -o src/common/linux/src_common_dumper_unittest-synth_elf_unittest.o `test -f 'src/common/linux/synth_elf_unittest.cc' || echo '$(srcdir)/'`src/common/linux/synth_elf_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/synth_elf_unittest.cc' object='src/common/linux/src_common_dumper_unittest-synth_elf_unittest.o' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-synth_elf_unittest.o `test -f 'src/common/linux/synth_elf_unittest.cc' || echo '$(srcdir)/'`src/common/linux/synth_elf_unittest.cc
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-synth_elf_unittest.obj: src/common/linux/synth_elf_unittest.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-synth_elf_unittest.obj -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Tpo -c -o src/common/linux/src_common_dumper_unittest-synth_elf_unittest.obj `if test -f 'src/common/linux/synth_elf_unittest.cc'; then $(CYGPATH_W) 'src/common/linux/synth_elf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/synth_elf_unittest.cc'; fi`
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-synth_elf_unittest.Po
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/common/linux/synth_elf_unittest.cc' object='src/common/linux/src_common_dumper_unittest-synth_elf_unittest.obj' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/common/linux/src_common_dumper_unittest-synth_elf_unittest.obj `if test -f 'src/common/linux/synth_elf_unittest.cc'; then $(CYGPATH_W) 'src/common/linux/synth_elf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/src/common/linux/synth_elf_unittest.cc'; fi`
|
||||
|
||||
src/common/linux/src_common_dumper_unittest-file_id.o: src/common/linux/file_id.cc
|
||||
@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_common_dumper_unittest_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/common/linux/src_common_dumper_unittest-file_id.o -MD -MP -MF src/common/linux/$(DEPDIR)/src_common_dumper_unittest-file_id.Tpo -c -o src/common/linux/src_common_dumper_unittest-file_id.o `test -f 'src/common/linux/file_id.cc' || echo '$(srcdir)/'`src/common/linux/file_id.cc
|
||||
@am__fastdepCXX_TRUE@ $(am__mv) src/common/linux/$(DEPDIR)/src_common_dumper_unittest-file_id.Tpo src/common/linux/$(DEPDIR)/src_common_dumper_unittest-file_id.Po
|
||||
|
@ -40,6 +40,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
@ -34,6 +34,8 @@
|
||||
|
||||
#include "common/dwarf_line_to_module.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// Trying to support Windows paths in a reasonable way adds a lot of
|
||||
// variations to test; it would be better to just put off dealing with
|
||||
// it until we actually have to deal with DWARF on Windows.
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@ -721,25 +722,32 @@ std::string BaseFileName(const std::string &filename) {
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
bool WriteSymbolFile(const std::string &obj_file,
|
||||
const std::string &debug_dir, FILE *sym_file) {
|
||||
MmapWrapper map_wrapper;
|
||||
ElfW(Ehdr) *elf_header = NULL;
|
||||
if (!LoadELF(obj_file, &map_wrapper, &elf_header))
|
||||
// Not explicitly exported, but not static so it can be used in unit tests.
|
||||
// Ideally obj_file would be const, but internally this code does write
|
||||
// to some ELF header fields to make its work simpler.
|
||||
bool WriteSymbolFileInternal(uint8_t* obj_file,
|
||||
const std::string &obj_filename,
|
||||
const std::string &debug_dir,
|
||||
std::ostream &sym_stream) {
|
||||
ElfW(Ehdr) *elf_header = reinterpret_cast<ElfW(Ehdr) *>(obj_file);
|
||||
|
||||
if (!IsValidElf(elf_header)) {
|
||||
fprintf(stderr, "Not a valid ELF file: %s\n", obj_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char identifier[16];
|
||||
google_breakpad::FileID file_id(obj_file.c_str());
|
||||
if (!file_id.ElfFileIdentifierFromMappedFile(elf_header, identifier)) {
|
||||
if (!google_breakpad::FileID::ElfFileIdentifierFromMappedFile(elf_header,
|
||||
identifier)) {
|
||||
fprintf(stderr, "%s: unable to generate file identifier\n",
|
||||
obj_file.c_str());
|
||||
obj_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *architecture = ElfArchitecture(elf_header);
|
||||
if (!architecture) {
|
||||
fprintf(stderr, "%s: unrecognized ELF machine architecture: %d\n",
|
||||
obj_file.c_str(), elf_header->e_machine);
|
||||
obj_filename.c_str(), elf_header->e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -748,13 +756,13 @@ bool WriteSymbolFile(const std::string &obj_file,
|
||||
if (!ElfEndianness(elf_header, &big_endian))
|
||||
return false;
|
||||
|
||||
std::string name = BaseFileName(obj_file);
|
||||
std::string name = BaseFileName(obj_filename);
|
||||
std::string os = "Linux";
|
||||
std::string id = FormatIdentifier(identifier);
|
||||
|
||||
LoadSymbolsInfo info(debug_dir);
|
||||
Module module(name, os, architecture, id);
|
||||
if (!LoadSymbols(obj_file, big_endian, elf_header, !debug_dir.empty(),
|
||||
if (!LoadSymbols(obj_filename, big_endian, elf_header, !debug_dir.empty(),
|
||||
&info, &module)) {
|
||||
const std::string debuglink_file = info.debuglink_file();
|
||||
if (debuglink_file.empty())
|
||||
@ -777,7 +785,7 @@ bool WriteSymbolFile(const std::string &obj_file,
|
||||
fprintf(stderr, "%s with ELF machine architecture %s does not match "
|
||||
"%s with ELF architecture %s\n",
|
||||
debuglink_file.c_str(), debug_architecture,
|
||||
obj_file.c_str(), architecture);
|
||||
obj_filename.c_str(), architecture);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -786,7 +794,7 @@ bool WriteSymbolFile(const std::string &obj_file,
|
||||
return false;
|
||||
if (debug_big_endian != big_endian) {
|
||||
fprintf(stderr, "%s and %s does not match in endianness\n",
|
||||
obj_file.c_str(), debuglink_file.c_str());
|
||||
obj_filename.c_str(), debuglink_file.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -795,10 +803,22 @@ bool WriteSymbolFile(const std::string &obj_file,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!module.Write(sym_file))
|
||||
if (!module.Write(sym_stream))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteSymbolFile(const std::string &obj_file,
|
||||
const std::string &debug_dir,
|
||||
std::ostream &sym_stream) {
|
||||
MmapWrapper map_wrapper;
|
||||
ElfW(Ehdr) *elf_header = NULL;
|
||||
if (!LoadELF(obj_file, &map_wrapper, &elf_header))
|
||||
return false;
|
||||
|
||||
return WriteSymbolFileInternal(reinterpret_cast<uint8_t*>(elf_header),
|
||||
obj_file, debug_dir, sym_stream);
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@ -35,19 +35,19 @@
|
||||
#ifndef COMMON_LINUX_DUMP_SYMBOLS_H__
|
||||
#define COMMON_LINUX_DUMP_SYMBOLS_H__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Find all the debugging information in OBJ_FILE, an ELF executable
|
||||
// or shared library, and write it to SYM_FILE in the Breakpad symbol
|
||||
// or shared library, and write it to SYM_STREAM in the Breakpad symbol
|
||||
// file format.
|
||||
// If OBJ_FILE has been stripped but contains a .gnu_debuglink section,
|
||||
// then look for the debug file in DEBUG_DIR.
|
||||
bool WriteSymbolFile(const std::string &obj_file,
|
||||
const std::string &debug_dir, FILE *sym_file);
|
||||
const std::string &debug_dir,
|
||||
std::ostream &sym_stream);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
|
162
src/common/linux/dump_symbols_unittest.cc
Normal file
162
src/common/linux/dump_symbols_unittest.cc
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2011 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Original author: Ted Mielczarek <ted.mielczarek@gmail.com>
|
||||
|
||||
// dump_symbols_unittest.cc:
|
||||
// Unittests for google_breakpad::DumpSymbols
|
||||
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/synth_elf.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
bool WriteSymbolFileInternal(uint8_t* obj_file,
|
||||
const std::string &obj_filename,
|
||||
const std::string &debug_dir,
|
||||
std::ostream &sym_stream);
|
||||
}
|
||||
|
||||
using google_breakpad::synth_elf::ELF;
|
||||
using google_breakpad::synth_elf::StringTable;
|
||||
using google_breakpad::synth_elf::SymbolTable;
|
||||
using google_breakpad::test_assembler::kLittleEndian;
|
||||
using google_breakpad::test_assembler::Section;
|
||||
using google_breakpad::WriteSymbolFileInternal;
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::vector;
|
||||
using ::testing::Test;
|
||||
|
||||
class DumpSymbols : public Test {
|
||||
public:
|
||||
void GetElfContents(ELF& elf) {
|
||||
string contents;
|
||||
ASSERT_TRUE(elf.GetContents(&contents));
|
||||
ASSERT_LT(0, contents.size());
|
||||
|
||||
elfdata_v.clear();
|
||||
elfdata_v.insert(elfdata_v.begin(), contents.begin(), contents.end());
|
||||
elfdata = &elfdata_v[0];
|
||||
}
|
||||
|
||||
vector<uint8_t> elfdata_v;
|
||||
uint8_t* elfdata;
|
||||
};
|
||||
|
||||
TEST_F(DumpSymbols, Invalid) {
|
||||
Elf32_Ehdr header;
|
||||
memset(&header, 0, sizeof(header));
|
||||
stringstream s;
|
||||
EXPECT_FALSE(WriteSymbolFileInternal(reinterpret_cast<uint8_t*>(&header),
|
||||
"foo",
|
||||
"",
|
||||
s));
|
||||
}
|
||||
|
||||
// TODO(ted): Fix the dump_symbols code to deal with cross-word-size
|
||||
// ELF files.
|
||||
#if __ELF_NATIVE_CLASS == 32
|
||||
TEST_F(DumpSymbols, SimplePublic32) {
|
||||
ELF elf(EM_386, ELFCLASS32, kLittleEndian);
|
||||
// Zero out text section for simplicity.
|
||||
Section text(kLittleEndian);
|
||||
text.Append(4096, 0);
|
||||
elf.AddSection(".text", text, SHT_PROGBITS);
|
||||
|
||||
// Add a public symbol.
|
||||
StringTable table(kLittleEndian);
|
||||
SymbolTable syms(kLittleEndian, 4, table);
|
||||
syms.AddSymbol("superfunc", (uint32_t)0x1000, (uint32_t)0x10,
|
||||
ELF32_ST_INFO(STB_GLOBAL, STT_FUNC),
|
||||
SHN_UNDEF + 1);
|
||||
int index = elf.AddSection(".dynstr", table, SHT_STRTAB);
|
||||
elf.AddSection(".dynsym", syms,
|
||||
SHT_DYNSYM, // type
|
||||
SHF_ALLOC, // flags
|
||||
0, // addr
|
||||
index, // link
|
||||
sizeof(Elf32_Sym)); // entsize
|
||||
|
||||
elf.Finish();
|
||||
GetElfContents(elf);
|
||||
|
||||
stringstream s;
|
||||
ASSERT_TRUE(WriteSymbolFileInternal(elfdata,
|
||||
"foo",
|
||||
"",
|
||||
s));
|
||||
EXPECT_EQ("MODULE Linux x86 000000000000000000000000000000000 foo\n"
|
||||
"PUBLIC 1000 0 superfunc\n",
|
||||
s.str());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __ELF_NATIVE_CLASS == 64
|
||||
TEST_F(DumpSymbols, SimplePublic64) {
|
||||
ELF elf(EM_X86_64, ELFCLASS64, kLittleEndian);
|
||||
// Zero out text section for simplicity.
|
||||
Section text(kLittleEndian);
|
||||
text.Append(4096, 0);
|
||||
elf.AddSection(".text", text, SHT_PROGBITS);
|
||||
|
||||
// Add a public symbol.
|
||||
StringTable table(kLittleEndian);
|
||||
SymbolTable syms(kLittleEndian, 8, table);
|
||||
syms.AddSymbol("superfunc", (uint64_t)0x1000, (uint64_t)0x10,
|
||||
ELF64_ST_INFO(STB_GLOBAL, STT_FUNC),
|
||||
SHN_UNDEF + 1);
|
||||
int index = elf.AddSection(".dynstr", table, SHT_STRTAB);
|
||||
elf.AddSection(".dynsym", syms,
|
||||
SHT_DYNSYM, // type
|
||||
SHF_ALLOC, // flags
|
||||
0, // addr
|
||||
index, // link
|
||||
sizeof(Elf64_Sym)); // entsize
|
||||
|
||||
elf.Finish();
|
||||
GetElfContents(elf);
|
||||
|
||||
stringstream s;
|
||||
ASSERT_TRUE(WriteSymbolFileInternal(elfdata,
|
||||
"foo",
|
||||
"",
|
||||
s));
|
||||
EXPECT_EQ("MODULE Linux x86_64 000000000000000000000000000000000 foo\n"
|
||||
"PUBLIC 1000 0 superfunc\n",
|
||||
s.str());
|
||||
}
|
||||
#endif
|
@ -39,14 +39,15 @@
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/elf_symbols_to_module.h"
|
||||
#include "common/linux/synth_elf.h"
|
||||
#include "common/module.h"
|
||||
#include "common/test_assembler.h"
|
||||
|
||||
using google_breakpad::Module;
|
||||
using google_breakpad::synth_elf::StringTable;
|
||||
using google_breakpad::test_assembler::Endianness;
|
||||
using google_breakpad::test_assembler::kBigEndian;
|
||||
using google_breakpad::test_assembler::kLittleEndian;
|
||||
using google_breakpad::test_assembler::kUnsetEndian;
|
||||
using google_breakpad::test_assembler::Label;
|
||||
using google_breakpad::test_assembler::Section;
|
||||
using ::testing::Test;
|
||||
@ -54,70 +55,6 @@ using ::testing::TestWithParam;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
// String Tables are used in ELF headers, add a class
|
||||
// for convenience.
|
||||
class StringTable : public Section {
|
||||
public:
|
||||
StringTable(Endianness endianness = kUnsetEndian)
|
||||
: Section(endianness) {
|
||||
start() = 0;
|
||||
empty_string = Add("");
|
||||
}
|
||||
|
||||
// Add the string s to the string table, and return
|
||||
// a label containing the offset into the string table
|
||||
// at which it was added.
|
||||
Label Add(const string& s) {
|
||||
Label string_label(Here());
|
||||
AppendCString(s);
|
||||
return string_label;
|
||||
}
|
||||
|
||||
// All StringTables contain an empty string as their first
|
||||
// entry.
|
||||
Label empty_string;
|
||||
};
|
||||
|
||||
class StringTableTest : public Test {
|
||||
public:
|
||||
StringTableTest() : table(kLittleEndian) {}
|
||||
|
||||
StringTable table;
|
||||
};
|
||||
|
||||
TEST_F(StringTableTest, Empty) {
|
||||
string contents;
|
||||
ASSERT_TRUE(table.GetContents(&contents));
|
||||
const string kExpectedContents = "\0";
|
||||
EXPECT_EQ(0,
|
||||
memcmp(kExpectedContents.c_str(), contents.c_str(), table.Size()));
|
||||
ASSERT_TRUE(table.empty_string.IsKnownConstant());
|
||||
EXPECT_EQ(0, table.empty_string.Value());
|
||||
}
|
||||
|
||||
TEST_F(StringTableTest, Basic) {
|
||||
const string s1("table fills with strings");
|
||||
const string s2("offsets preserved as labels");
|
||||
const string s3("verified with tests");
|
||||
const string kExpectedContents =
|
||||
"\0table fills with strings\0"
|
||||
"offsets preserved as labels\0"
|
||||
"verified with tests\0";
|
||||
Label l1(table.Add(s1));
|
||||
Label l2(table.Add(s2));
|
||||
Label l3(table.Add(s3));
|
||||
string contents;
|
||||
ASSERT_TRUE(table.GetContents(&contents));
|
||||
EXPECT_EQ(0,
|
||||
memcmp(kExpectedContents.c_str(), contents.c_str(), table.Size()));
|
||||
// empty_string is at zero, other strings start at 1.
|
||||
ASSERT_TRUE(l1.IsKnownConstant());
|
||||
EXPECT_EQ(1, l1.Value());
|
||||
// Each string has an extra byte for a trailing null.
|
||||
EXPECT_EQ(1 + s1.length() + 1, l2.Value());
|
||||
EXPECT_EQ(1 + s1.length() + 1 + s2.length() + 1, l3.Value());
|
||||
}
|
||||
|
||||
class ELFSymbolsToModuleTestFixture {
|
||||
public:
|
||||
ELFSymbolsToModuleTestFixture(Endianness endianness,
|
||||
|
174
src/common/linux/synth_elf.cc
Normal file
174
src/common/linux/synth_elf.cc
Normal file
@ -0,0 +1,174 @@
|
||||
#include "common/linux/synth_elf.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <elf.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
namespace synth_elf {
|
||||
|
||||
ELF::ELF(uint16_t machine,
|
||||
uint8_t file_class,
|
||||
Endianness endianness)
|
||||
: Section(endianness),
|
||||
addr_size_(file_class == ELFCLASS64 ? 8 : 4),
|
||||
program_count_(0),
|
||||
section_count_(0),
|
||||
section_header_table_(endianness),
|
||||
section_header_strings_(endianness) {
|
||||
// Could add support for more machine types here if needed.
|
||||
assert(machine == EM_386 ||
|
||||
machine == EM_X86_64 ||
|
||||
machine == EM_ARM);
|
||||
assert(file_class == ELFCLASS32 || file_class == ELFCLASS64);
|
||||
|
||||
start() = 0;
|
||||
// Add ELF header
|
||||
// e_ident
|
||||
// EI_MAG0...EI_MAG3
|
||||
D8(ELFMAG0);
|
||||
D8(ELFMAG1);
|
||||
D8(ELFMAG2);
|
||||
D8(ELFMAG3);
|
||||
// EI_CLASS
|
||||
D8(file_class);
|
||||
// EI_DATA
|
||||
D8(endianness == kLittleEndian ? ELFDATA2LSB : ELFDATA2MSB);
|
||||
// EI_VERSION
|
||||
D8(EV_CURRENT);
|
||||
// EI_OSABI
|
||||
D8(ELFOSABI_SYSV);
|
||||
// EI_ABIVERSION
|
||||
D8(0);
|
||||
// EI_PAD
|
||||
Append(7, 0);
|
||||
assert(Size() == EI_NIDENT);
|
||||
|
||||
// e_type
|
||||
D16(ET_EXEC); //TODO: allow passing ET_DYN?
|
||||
// e_machine
|
||||
D16(machine);
|
||||
// e_version
|
||||
D32(EV_CURRENT);
|
||||
// e_entry
|
||||
Append(endianness, addr_size_, 0);
|
||||
// e_phoff
|
||||
Append(endianness, addr_size_, program_header_label_);
|
||||
// e_shoff
|
||||
Append(endianness, addr_size_, section_header_label_);
|
||||
// e_flags
|
||||
D32(0);
|
||||
// e_ehsize
|
||||
D16(addr_size_ == 8 ? sizeof(Elf64_Ehdr) : sizeof(Elf32_Ehdr));
|
||||
// e_phentsize
|
||||
D16(addr_size_ == 8 ? sizeof(Elf64_Phdr) : sizeof(Elf32_Phdr));
|
||||
// e_phnum
|
||||
D16(program_count_label_);
|
||||
// e_shentsize
|
||||
D16(addr_size_ == 8 ? sizeof(Elf64_Shdr) : sizeof(Elf32_Shdr));
|
||||
// e_shnum
|
||||
D16(section_count_label_);
|
||||
// e_shstrndx
|
||||
D16(section_header_string_index_);
|
||||
|
||||
// Add an empty section for SHN_UNDEF.
|
||||
Section shn_undef;
|
||||
AddSection("", shn_undef, SHT_NULL);
|
||||
}
|
||||
|
||||
int ELF::AddSection(const string& name, const Section& section,
|
||||
uint32_t type, uint32_t flags, uint64_t addr,
|
||||
uint32_t link, uint64_t entsize, uint64_t offset) {
|
||||
Label offset_label;
|
||||
Label string_label(section_header_strings_.Add(name));
|
||||
size_t size = section.Size();
|
||||
|
||||
int index = section_count_;
|
||||
++section_count_;
|
||||
|
||||
section_header_table_
|
||||
// sh_name
|
||||
.D32(string_label)
|
||||
// sh_type
|
||||
.D32(type)
|
||||
// sh_flags
|
||||
.Append(endianness(), addr_size_, flags)
|
||||
// sh_addr
|
||||
.Append(endianness(), addr_size_, addr)
|
||||
// sh_offset
|
||||
.Append(endianness(), addr_size_, offset_label)
|
||||
// sh_size
|
||||
.Append(endianness(), addr_size_, size)
|
||||
// sh_link
|
||||
.D32(link)
|
||||
// sh_info
|
||||
.D32(0)
|
||||
// sh_addralign
|
||||
.Append(endianness(), addr_size_, 0)
|
||||
// sh_entsize
|
||||
.Append(endianness(), addr_size_, entsize);
|
||||
|
||||
// NULL and NOBITS sections have no content, so they
|
||||
// don't need to be written to the file.
|
||||
if (type == SHT_NULL) {
|
||||
offset_label = 0;
|
||||
} else if (type == SHT_NOBITS) {
|
||||
offset_label = offset;
|
||||
} else {
|
||||
Mark(&offset_label);
|
||||
Append(section);
|
||||
Align(4);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void ELF::Finish() {
|
||||
// Add the section header string table at the end.
|
||||
section_header_string_index_ = section_count_;
|
||||
//printf(".shstrtab size: %ld\n", section_header_strings_.Size());
|
||||
AddSection(".shstrtab", section_header_strings_, SHT_STRTAB);
|
||||
//printf("section_count_: %ld, sections_.size(): %ld\n",
|
||||
// section_count_, sections_.size());
|
||||
section_count_label_ = section_count_;
|
||||
program_count_label_ = program_count_;
|
||||
// TODO: allow adding entries to program header table
|
||||
program_header_label_ = 0;
|
||||
|
||||
// Section header table starts here.
|
||||
Mark(§ion_header_label_);
|
||||
Append(section_header_table_);
|
||||
}
|
||||
|
||||
SymbolTable::SymbolTable(Endianness endianness,
|
||||
size_t addr_size,
|
||||
StringTable& table) : Section(endianness),
|
||||
addr_size_(addr_size),
|
||||
table_(table) {
|
||||
assert(addr_size_ == 4 || addr_size_ == 8);
|
||||
}
|
||||
|
||||
void SymbolTable::AddSymbol(const string& name, uint32_t value,
|
||||
uint32_t size, unsigned info, uint16_t shndx) {
|
||||
assert(addr_size_ == 4);
|
||||
D32(table_.Add(name));
|
||||
D32(value);
|
||||
D32(size);
|
||||
D8(info);
|
||||
D8(0); // other
|
||||
D16(shndx);
|
||||
}
|
||||
|
||||
void SymbolTable::AddSymbol(const string& name, uint64_t value,
|
||||
uint64_t size, unsigned info, uint16_t shndx) {
|
||||
assert(addr_size_ == 8);
|
||||
D32(table_.Add(name));
|
||||
D8(info);
|
||||
D8(0); // other
|
||||
D16(shndx);
|
||||
D64(value);
|
||||
D64(size);
|
||||
}
|
||||
|
||||
} // namespace synth_elf
|
||||
} // namespace google_breakpad
|
||||
|
155
src/common/linux/synth_elf.h
Normal file
155
src/common/linux/synth_elf.h
Normal file
@ -0,0 +1,155 @@
|
||||
// -*- mode: C++ -*-
|
||||
|
||||
// Copyright (c) 2011, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Original author: Ted Mielczarek <ted.mielczarek@gmail.com>
|
||||
|
||||
// synth_elf.h: Interface to synth_elf::ELF: fake ELF generator.
|
||||
|
||||
#ifndef COMMON_LINUX_SYNTH_ELF_H_
|
||||
#define COMMON_LINUX_SYNTH_ELF_H_
|
||||
|
||||
#include "common/test_assembler.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace google_breakpad {
|
||||
namespace synth_elf {
|
||||
|
||||
using std::list;
|
||||
using std::map;
|
||||
using std::pair;
|
||||
using std::string;
|
||||
using test_assembler::Endianness;
|
||||
using test_assembler::kLittleEndian;
|
||||
using test_assembler::kUnsetEndian;
|
||||
using test_assembler::Label;
|
||||
using test_assembler::Section;
|
||||
|
||||
// String tables are common in ELF headers, so subclass Section
|
||||
// to make them easy to generate.
|
||||
class StringTable : public Section {
|
||||
public:
|
||||
StringTable(Endianness endianness = kUnsetEndian)
|
||||
: Section(endianness) {
|
||||
start() = 0;
|
||||
empty_string = Add("");
|
||||
}
|
||||
|
||||
// Add the string s to the string table, and return
|
||||
// a label containing the offset into the string table
|
||||
// at which it was added.
|
||||
Label Add(const string& s) {
|
||||
if (strings_.find(s) != strings_.end())
|
||||
return strings_[s];
|
||||
|
||||
Label string_label(Here());
|
||||
AppendCString(s);
|
||||
strings_[s] = string_label;
|
||||
return string_label;
|
||||
}
|
||||
|
||||
// All StringTables contain an empty string as their first
|
||||
// entry.
|
||||
Label empty_string;
|
||||
|
||||
// Avoid inserting duplicate strings.
|
||||
map<string,Label> strings_;
|
||||
};
|
||||
|
||||
// A Section representing an entire ELF file.
|
||||
class ELF : public Section {
|
||||
public:
|
||||
ELF(uint16_t machine, // EM_386, etc
|
||||
uint8_t file_class, // ELFCLASS{32,64}
|
||||
Endianness endianness = kLittleEndian);
|
||||
|
||||
// Add the Section section to the section header table and append it
|
||||
// to the file. Returns the index of the section in the section
|
||||
// header table.
|
||||
int AddSection(const string& name, const Section& section,
|
||||
uint32_t type, uint32_t flags = 0, uint64_t addr = 0,
|
||||
uint32_t link = 0, uint64_t entsize = 0, uint64_t offset = 0);
|
||||
|
||||
// Write out all data. GetContents may be used after this.
|
||||
void Finish();
|
||||
|
||||
private:
|
||||
// Size of an address, in bytes.
|
||||
const size_t addr_size_;
|
||||
|
||||
// Offset to the program header table.
|
||||
Label program_header_label_;
|
||||
// Number of entries in the program header table.
|
||||
int program_count_;
|
||||
Label program_count_label_;
|
||||
|
||||
// Offset to the section header table.
|
||||
Label section_header_label_;
|
||||
// Number of entries in the section header table.
|
||||
int section_count_;
|
||||
Label section_count_label_;
|
||||
// The section header table itself.
|
||||
Section section_header_table_;
|
||||
|
||||
// Index of the section header string table in the section
|
||||
// header table.
|
||||
Label section_header_string_index_;
|
||||
// Section containing the names of section header table entries.
|
||||
StringTable section_header_strings_;
|
||||
};
|
||||
|
||||
// A class to build .symtab or .dynsym sections.
|
||||
class SymbolTable : public Section {
|
||||
public:
|
||||
// table is the StringTable that contains symbol names. The caller
|
||||
// must ensure that it remains alive for the life of the
|
||||
// SymbolTable.
|
||||
SymbolTable(Endianness endianness, size_t addr_size, StringTable& table);
|
||||
|
||||
// Add an Elf32_Sym.
|
||||
void AddSymbol(const string& name, uint32_t value,
|
||||
uint32_t size, unsigned info, uint16_t shndx);
|
||||
// Add an Elf64_Sym.
|
||||
void AddSymbol(const string& name, uint64_t value,
|
||||
uint64_t size, unsigned info, uint16_t shndx);
|
||||
|
||||
private:
|
||||
size_t addr_size_;
|
||||
StringTable& table_;
|
||||
};
|
||||
|
||||
} // namespace synth_elf
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // COMMON_LINUX_SYNTH_ELF_H_
|
265
src/common/linux/synth_elf_unittest.cc
Normal file
265
src/common/linux/synth_elf_unittest.cc
Normal file
@ -0,0 +1,265 @@
|
||||
// Copyright (c) 2011 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Original author: Ted Mielczarek <ted.mielczarek@gmail.com>
|
||||
|
||||
// synth_elf_unittest.cc:
|
||||
// Unittests for google_breakpad::synth_elf::ELF
|
||||
|
||||
#include <elf.h>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/synth_elf.h"
|
||||
|
||||
using google_breakpad::synth_elf::ELF;
|
||||
using google_breakpad::synth_elf::StringTable;
|
||||
using google_breakpad::synth_elf::SymbolTable;
|
||||
using google_breakpad::test_assembler::Endianness;
|
||||
using google_breakpad::test_assembler::kBigEndian;
|
||||
using google_breakpad::test_assembler::kLittleEndian;
|
||||
using google_breakpad::test_assembler::Label;
|
||||
using std::string;
|
||||
using ::testing::Test;
|
||||
|
||||
class StringTableTest : public Test {
|
||||
public:
|
||||
StringTableTest() : table(kLittleEndian) {}
|
||||
|
||||
StringTable table;
|
||||
};
|
||||
|
||||
TEST_F(StringTableTest, Empty) {
|
||||
EXPECT_EQ(1, table.Size());
|
||||
string contents;
|
||||
ASSERT_TRUE(table.GetContents(&contents));
|
||||
const char* kExpectedContents = "\0";
|
||||
EXPECT_EQ(0, memcmp(kExpectedContents,
|
||||
contents.c_str(),
|
||||
contents.size()));
|
||||
ASSERT_TRUE(table.empty_string.IsKnownConstant());
|
||||
EXPECT_EQ(0, table.empty_string.Value());
|
||||
}
|
||||
|
||||
TEST_F(StringTableTest, Basic) {
|
||||
const string s1("table fills with strings");
|
||||
const string s2("offsets preserved as labels");
|
||||
const string s3("verified with tests");
|
||||
const char* kExpectedContents =
|
||||
"\0table fills with strings\0"
|
||||
"offsets preserved as labels\0"
|
||||
"verified with tests\0";
|
||||
Label l1(table.Add(s1));
|
||||
Label l2(table.Add(s2));
|
||||
Label l3(table.Add(s3));
|
||||
string contents;
|
||||
ASSERT_TRUE(table.GetContents(&contents));
|
||||
EXPECT_EQ(0, memcmp(kExpectedContents,
|
||||
contents.c_str(),
|
||||
contents.size()));
|
||||
// empty_string is at zero, other strings start at 1.
|
||||
ASSERT_TRUE(l1.IsKnownConstant());
|
||||
EXPECT_EQ(1, l1.Value());
|
||||
// Each string has an extra byte for a trailing null.
|
||||
EXPECT_EQ(1 + s1.length() + 1, l2.Value());
|
||||
EXPECT_EQ(1 + s1.length() + 1 + s2.length() + 1, l3.Value());
|
||||
}
|
||||
|
||||
TEST_F(StringTableTest, Duplicates) {
|
||||
const string s1("string 1");
|
||||
const string s2("string 2");
|
||||
const string s3("");
|
||||
const char* kExpectedContents = "\0string 1\0string 2\0";
|
||||
Label l1(table.Add(s1));
|
||||
Label l2(table.Add(s2));
|
||||
// Adding strings twice should return the same Label.
|
||||
Label l3(table.Add(s3));
|
||||
Label l4(table.Add(s2));
|
||||
string contents;
|
||||
ASSERT_TRUE(table.GetContents(&contents));
|
||||
EXPECT_EQ(0, memcmp(kExpectedContents,
|
||||
contents.c_str(),
|
||||
contents.size()));
|
||||
EXPECT_EQ(0, table.empty_string.Value());
|
||||
EXPECT_EQ(table.empty_string.Value(), l3.Value());
|
||||
EXPECT_EQ(l2.Value(), l4.Value());
|
||||
}
|
||||
|
||||
class SymbolTableTest : public Test {};
|
||||
|
||||
TEST_F(SymbolTableTest, Simple32) {
|
||||
StringTable table(kLittleEndian);
|
||||
SymbolTable syms(kLittleEndian, 4, table);
|
||||
|
||||
const string kFuncName1 = "superfunc";
|
||||
const uint32_t kFuncAddr1 = 0x10001000;
|
||||
const uint32_t kFuncSize1 = 0x10;
|
||||
const string kFuncName2 = "awesomefunc";
|
||||
const uint32_t kFuncAddr2 = 0x20002000;
|
||||
const uint32_t kFuncSize2 = 0x2f;
|
||||
const string kFuncName3 = "megafunc";
|
||||
const uint32_t kFuncAddr3 = 0x30003000;
|
||||
const uint32_t kFuncSize3 = 0x3c;
|
||||
|
||||
syms.AddSymbol(kFuncName1, kFuncAddr1, kFuncSize1,
|
||||
ELF32_ST_INFO(STB_GLOBAL, STT_FUNC),
|
||||
SHN_UNDEF + 1);
|
||||
syms.AddSymbol(kFuncName2, kFuncAddr2, kFuncSize2,
|
||||
ELF32_ST_INFO(STB_LOCAL, STT_FUNC),
|
||||
SHN_UNDEF + 2);
|
||||
syms.AddSymbol(kFuncName3, kFuncAddr3, kFuncSize3,
|
||||
ELF32_ST_INFO(STB_LOCAL, STT_FUNC),
|
||||
SHN_UNDEF + 3);
|
||||
|
||||
const char kExpectedStringTable[] = "\0superfunc\0awesomefunc\0megafunc";
|
||||
const size_t kExpectedStringTableSize = sizeof(kExpectedStringTable);
|
||||
EXPECT_EQ(kExpectedStringTableSize, table.Size());
|
||||
string table_contents;
|
||||
table.GetContents(&table_contents);
|
||||
EXPECT_EQ(0, memcmp(kExpectedStringTable,
|
||||
table_contents.c_str(),
|
||||
table_contents.size()));
|
||||
|
||||
const uint8_t kExpectedSymbolContents[] = {
|
||||
// Symbol 1
|
||||
0x01, 0x00, 0x00, 0x00, // name
|
||||
0x00, 0x10, 0x00, 0x10, // value
|
||||
0x10, 0x00, 0x00, 0x00, // size
|
||||
ELF32_ST_INFO(STB_GLOBAL, STT_FUNC), // info
|
||||
0x00, // other
|
||||
0x01, 0x00, // shndx
|
||||
// Symbol 2
|
||||
0x0B, 0x00, 0x00, 0x00, // name
|
||||
0x00, 0x20, 0x00, 0x20, // value
|
||||
0x2f, 0x00, 0x00, 0x00, // size
|
||||
ELF32_ST_INFO(STB_LOCAL, STT_FUNC), // info
|
||||
0x00, // other
|
||||
0x02, 0x00, // shndx
|
||||
// Symbol 3
|
||||
0x17, 0x00, 0x00, 0x00, // name
|
||||
0x00, 0x30, 0x00, 0x30, // value
|
||||
0x3c, 0x00, 0x00, 0x00, // size
|
||||
ELF32_ST_INFO(STB_LOCAL, STT_FUNC), // info
|
||||
0x00, // other
|
||||
0x03, 0x00, // shndx
|
||||
};
|
||||
const size_t kExpectedSymbolSize = sizeof(kExpectedSymbolContents);
|
||||
EXPECT_EQ(kExpectedSymbolSize, syms.Size());
|
||||
|
||||
string symbol_contents;
|
||||
syms.GetContents(&symbol_contents);
|
||||
EXPECT_EQ(0, memcmp(kExpectedSymbolContents,
|
||||
symbol_contents.c_str(),
|
||||
symbol_contents.size()));
|
||||
}
|
||||
|
||||
class BasicElf : public Test {};
|
||||
|
||||
// Doesn't seem worthwhile writing the tests to be endian-independent
|
||||
// when they're unlikely to ever be run on big-endian systems.
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
|
||||
TEST_F(BasicElf, EmptyLE32) {
|
||||
const size_t kStringTableSize = sizeof("\0.shstrtab");
|
||||
const size_t kStringTableAlign = 4 - kStringTableSize % 4;
|
||||
const size_t kExpectedSize = sizeof(Elf32_Ehdr) +
|
||||
// Two sections, SHT_NULL + the section header string table.
|
||||
2 * sizeof(Elf32_Shdr) +
|
||||
kStringTableSize + kStringTableAlign;
|
||||
|
||||
ELF elf(EM_386, ELFCLASS32, kLittleEndian);
|
||||
elf.Finish();
|
||||
EXPECT_EQ(kExpectedSize, elf.Size());
|
||||
|
||||
string contents;
|
||||
ASSERT_TRUE(elf.GetContents(&contents));
|
||||
ASSERT_EQ(kExpectedSize, contents.size());
|
||||
const Elf32_Ehdr* header =
|
||||
reinterpret_cast<const Elf32_Ehdr*>(contents.data());
|
||||
const uint8_t kIdent[] = {
|
||||
ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3,
|
||||
ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_SYSV,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
EXPECT_EQ(0, memcmp(kIdent, header->e_ident, sizeof(kIdent)));
|
||||
EXPECT_EQ(ET_EXEC, header->e_type);
|
||||
EXPECT_EQ(EM_386, header->e_machine);
|
||||
EXPECT_EQ(EV_CURRENT, header->e_version);
|
||||
EXPECT_EQ(0, header->e_entry);
|
||||
EXPECT_EQ(0, header->e_phoff);
|
||||
EXPECT_EQ(sizeof(Elf32_Ehdr) + kStringTableSize + kStringTableAlign,
|
||||
header->e_shoff);
|
||||
EXPECT_EQ(0, header->e_flags);
|
||||
EXPECT_EQ(sizeof(Elf32_Ehdr), header->e_ehsize);
|
||||
EXPECT_EQ(sizeof(Elf32_Phdr), header->e_phentsize);
|
||||
EXPECT_EQ(0, header->e_phnum);
|
||||
EXPECT_EQ(sizeof(Elf32_Shdr), header->e_shentsize);
|
||||
EXPECT_EQ(2, header->e_shnum);
|
||||
EXPECT_EQ(1, header->e_shstrndx);
|
||||
}
|
||||
|
||||
TEST_F(BasicElf, EmptyLE64) {
|
||||
const size_t kStringTableSize = sizeof("\0.shstrtab");
|
||||
const size_t kStringTableAlign = 4 - kStringTableSize % 4;
|
||||
const size_t kExpectedSize = sizeof(Elf64_Ehdr) +
|
||||
// Two sections, SHT_NULL + the section header string table.
|
||||
2 * sizeof(Elf64_Shdr) +
|
||||
kStringTableSize + kStringTableAlign;
|
||||
|
||||
ELF elf(EM_X86_64, ELFCLASS64, kLittleEndian);
|
||||
elf.Finish();
|
||||
EXPECT_EQ(kExpectedSize, elf.Size());
|
||||
|
||||
string contents;
|
||||
ASSERT_TRUE(elf.GetContents(&contents));
|
||||
ASSERT_EQ(kExpectedSize, contents.size());
|
||||
const Elf64_Ehdr* header =
|
||||
reinterpret_cast<const Elf64_Ehdr*>(contents.data());
|
||||
const uint8_t kIdent[] = {
|
||||
ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3,
|
||||
ELFCLASS64, ELFDATA2LSB, EV_CURRENT, ELFOSABI_SYSV,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
EXPECT_EQ(0, memcmp(kIdent, header->e_ident, sizeof(kIdent)));
|
||||
EXPECT_EQ(ET_EXEC, header->e_type);
|
||||
EXPECT_EQ(EM_X86_64, header->e_machine);
|
||||
EXPECT_EQ(EV_CURRENT, header->e_version);
|
||||
EXPECT_EQ(0, header->e_entry);
|
||||
EXPECT_EQ(0, header->e_phoff);
|
||||
EXPECT_EQ(sizeof(Elf64_Ehdr) + kStringTableSize + kStringTableAlign,
|
||||
header->e_shoff);
|
||||
EXPECT_EQ(0, header->e_flags);
|
||||
EXPECT_EQ(sizeof(Elf64_Ehdr), header->e_ehsize);
|
||||
EXPECT_EQ(sizeof(Elf64_Phdr), header->e_phentsize);
|
||||
EXPECT_EQ(0, header->e_phnum);
|
||||
EXPECT_EQ(sizeof(Elf64_Shdr), header->e_shentsize);
|
||||
EXPECT_EQ(2, header->e_shnum);
|
||||
EXPECT_EQ(1, header->e_shstrndx);
|
||||
}
|
||||
|
||||
#endif // defined(__i386__) || defined(__x86_64__)
|
@ -35,10 +35,18 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::dec;
|
||||
using std::endl;
|
||||
using std::hex;
|
||||
|
||||
|
||||
Module::Module(const string &name, const string &os,
|
||||
const string &architecture, const string &id) :
|
||||
name_(name),
|
||||
@ -182,22 +190,20 @@ bool Module::ReportError() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Module::WriteRuleMap(const RuleMap &rule_map, FILE *stream) {
|
||||
bool Module::WriteRuleMap(const RuleMap &rule_map, std::ostream &stream) {
|
||||
for (RuleMap::const_iterator it = rule_map.begin();
|
||||
it != rule_map.end(); it++) {
|
||||
if (it != rule_map.begin() &&
|
||||
0 > putc(' ', stream))
|
||||
return false;
|
||||
if (0 > fprintf(stream, "%s: %s", it->first.c_str(), it->second.c_str()))
|
||||
return false;
|
||||
if (it != rule_map.begin())
|
||||
stream << ' ';
|
||||
stream << it->first << ": " << it->second;
|
||||
}
|
||||
return true;
|
||||
return stream.good();
|
||||
}
|
||||
|
||||
bool Module::Write(FILE *stream) {
|
||||
if (0 > fprintf(stream, "MODULE %s %s %s %s\n",
|
||||
os_.c_str(), architecture_.c_str(), id_.c_str(),
|
||||
name_.c_str()))
|
||||
bool Module::Write(std::ostream &stream) {
|
||||
stream << "MODULE " << os_ << " " << architecture_ << " "
|
||||
<< id_ << " " << name_ << endl;
|
||||
if (!stream.good())
|
||||
return ReportError();
|
||||
|
||||
AssignSourceIds();
|
||||
@ -207,8 +213,8 @@ bool Module::Write(FILE *stream) {
|
||||
file_it != files_.end(); file_it++) {
|
||||
File *file = file_it->second;
|
||||
if (file->source_id >= 0) {
|
||||
if (0 > fprintf(stream, "FILE %d %s\n",
|
||||
file->source_id, file->name.c_str()))
|
||||
stream << "FILE " << file->source_id << " " << file->name << endl;
|
||||
if (!stream.good())
|
||||
return ReportError();
|
||||
}
|
||||
}
|
||||
@ -217,29 +223,35 @@ bool Module::Write(FILE *stream) {
|
||||
for (FunctionSet::const_iterator func_it = functions_.begin();
|
||||
func_it != functions_.end(); func_it++) {
|
||||
Function *func = *func_it;
|
||||
if (0 > fprintf(stream, "FUNC %llx %llx %llx %s\n",
|
||||
(unsigned long long) (func->address - load_address_),
|
||||
(unsigned long long) func->size,
|
||||
(unsigned long long) func->parameter_size,
|
||||
func->name.c_str()))
|
||||
stream << "FUNC " << hex
|
||||
<< (func->address - load_address_) << " "
|
||||
<< func->size << " "
|
||||
<< func->parameter_size << " "
|
||||
<< func->name << dec << endl;
|
||||
|
||||
if (!stream.good())
|
||||
return ReportError();
|
||||
for (vector<Line>::iterator line_it = func->lines.begin();
|
||||
line_it != func->lines.end(); line_it++)
|
||||
if (0 > fprintf(stream, "%llx %llx %d %d\n",
|
||||
(unsigned long long) (line_it->address - load_address_),
|
||||
(unsigned long long) line_it->size,
|
||||
line_it->number,
|
||||
line_it->file->source_id))
|
||||
line_it != func->lines.end(); line_it++) {
|
||||
stream << hex
|
||||
<< (line_it->address - load_address_) << " "
|
||||
<< line_it->size << " "
|
||||
<< dec
|
||||
<< line_it->number << " "
|
||||
<< line_it->file->source_id << endl;
|
||||
if (!stream.good())
|
||||
return ReportError();
|
||||
}
|
||||
}
|
||||
|
||||
// Write out 'PUBLIC' records.
|
||||
for (ExternSet::const_iterator extern_it = externs_.begin();
|
||||
extern_it != externs_.end(); extern_it++) {
|
||||
Extern *ext = *extern_it;
|
||||
if (0 > fprintf(stream, "PUBLIC %llx 0 %s\n",
|
||||
(unsigned long long) (ext->address - load_address_),
|
||||
ext->name.c_str()))
|
||||
stream << "PUBLIC " << hex
|
||||
<< (ext->address - load_address_) << " 0 "
|
||||
<< ext->name << dec << endl;
|
||||
if (!stream.good())
|
||||
return ReportError();
|
||||
}
|
||||
|
||||
@ -248,21 +260,25 @@ bool Module::Write(FILE *stream) {
|
||||
for (frame_it = stack_frame_entries_.begin();
|
||||
frame_it != stack_frame_entries_.end(); frame_it++) {
|
||||
StackFrameEntry *entry = *frame_it;
|
||||
if (0 > fprintf(stream, "STACK CFI INIT %llx %llx ",
|
||||
(unsigned long long) entry->address - load_address_,
|
||||
(unsigned long long) entry->size)
|
||||
|| !WriteRuleMap(entry->initial_rules, stream)
|
||||
|| 0 > putc('\n', stream))
|
||||
stream << "STACK CFI INIT " << hex
|
||||
<< (entry->address - load_address_) << " "
|
||||
<< entry->size << " " << dec;
|
||||
if (!stream.good()
|
||||
|| !WriteRuleMap(entry->initial_rules, stream))
|
||||
return ReportError();
|
||||
|
||||
stream << endl;
|
||||
|
||||
// Write out this entry's delta rules as 'STACK CFI' records.
|
||||
for (RuleChangeMap::const_iterator delta_it = entry->rule_changes.begin();
|
||||
delta_it != entry->rule_changes.end(); delta_it++) {
|
||||
if (0 > fprintf(stream, "STACK CFI %llx ",
|
||||
(unsigned long long) delta_it->first - load_address_)
|
||||
|| !WriteRuleMap(delta_it->second, stream)
|
||||
|| 0 > putc('\n', stream))
|
||||
stream << "STACK CFI " << hex
|
||||
<< (delta_it->first - load_address_) << " " << dec;
|
||||
if (!stream.good()
|
||||
|| !WriteRuleMap(delta_it->second, stream))
|
||||
return ReportError();
|
||||
|
||||
stream << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,7 @@
|
||||
#ifndef COMMON_LINUX_MODULE_H__
|
||||
#define COMMON_LINUX_MODULE_H__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@ -264,7 +263,7 @@ class Module {
|
||||
// - the functions added via AddFunctions, each with its lines.
|
||||
// Addresses in the output are all relative to the load address
|
||||
// established by SetLoadAddress.
|
||||
bool Write(FILE *stream);
|
||||
bool Write(std::ostream &stream);
|
||||
|
||||
private:
|
||||
|
||||
@ -275,7 +274,7 @@ class Module {
|
||||
// Write RULE_MAP to STREAM, in the form appropriate for 'STACK CFI'
|
||||
// records, without a final newline. Return true if all goes well;
|
||||
// if an error occurs, return false, and leave errno set.
|
||||
static bool WriteRuleMap(const RuleMap &rule_map, FILE *stream);
|
||||
static bool WriteRuleMap(const RuleMap &rule_map, std::ostream &stream);
|
||||
|
||||
// Module header entries.
|
||||
string name_, os_, architecture_, id_;
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
@ -44,53 +45,10 @@
|
||||
|
||||
using google_breakpad::Module;
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::vector;
|
||||
using testing::ContainerEq;
|
||||
|
||||
// Return a FILE * referring to a temporary file that will be deleted
|
||||
// automatically when the stream is closed or the program exits.
|
||||
static FILE *checked_tmpfile() {
|
||||
FILE *f = tmpfile();
|
||||
if (!f) {
|
||||
fprintf(stderr, "error creating temporary file: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
// Read from STREAM until end of file, and return the contents as a
|
||||
// string.
|
||||
static string checked_read(FILE *stream) {
|
||||
string contents;
|
||||
int c;
|
||||
while ((c = getc(stream)) != EOF)
|
||||
contents.push_back(c);
|
||||
if (ferror(stream)) {
|
||||
fprintf(stderr, "error reading temporary file contents: %s\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
// Apply 'fflush' to STREAM, and check for errors.
|
||||
static void checked_fflush(FILE *stream) {
|
||||
if (fflush(stream) == EOF) {
|
||||
fprintf(stderr, "error flushing temporary file stream: %s\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply 'fclose' to STREAM, and check for errors.
|
||||
static void checked_fclose(FILE *stream) {
|
||||
if (fclose(stream) == EOF) {
|
||||
fprintf(stderr, "error closing temporary file stream: %s\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static Module::Function *generate_duplicate_function(const string &name) {
|
||||
const Module::Address DUP_ADDRESS = 0xd35402aac7a7ad5cLL;
|
||||
const Module::Address DUP_SIZE = 0x200b26e605f99071LL;
|
||||
@ -110,19 +68,16 @@ static Module::Function *generate_duplicate_function(const string &name) {
|
||||
#define MODULE_ID "id-string"
|
||||
|
||||
TEST(Write, Header) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n",
|
||||
contents.c_str());
|
||||
}
|
||||
|
||||
TEST(Write, OneLineFunc) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
Module::File *file = m.FindFile("file_name.cc");
|
||||
@ -136,11 +91,8 @@ TEST(Write, OneLineFunc) {
|
||||
function->lines.push_back(line);
|
||||
m.AddFunction(function);
|
||||
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n"
|
||||
"FILE 0 file_name.cc\n"
|
||||
"FUNC e165bf8023b9d9ab 1e4bb0eb1cbf5b09 772beee89114358a"
|
||||
@ -150,7 +102,7 @@ TEST(Write, OneLineFunc) {
|
||||
}
|
||||
|
||||
TEST(Write, RelativeLoadAddress) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Some source files. We will expect to see them in lexicographic order.
|
||||
@ -189,11 +141,8 @@ TEST(Write, RelativeLoadAddress) {
|
||||
// the module must work fine.
|
||||
m.SetLoadAddress(0x2ab698b0b6407073LL);
|
||||
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n"
|
||||
"FILE 0 filename-a.cc\n"
|
||||
"FILE 1 filename-b.cc\n"
|
||||
@ -247,12 +196,9 @@ TEST(Write, OmitUnusedFiles) {
|
||||
EXPECT_STREQ("filename3", vec[2]->name.c_str());
|
||||
EXPECT_NE(-1, vec[2]->source_id);
|
||||
|
||||
FILE *f = checked_tmpfile();
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
stringstream s;
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n"
|
||||
"FILE 0 filename1\n"
|
||||
"FILE 1 filename3\n"
|
||||
@ -264,7 +210,7 @@ TEST(Write, OmitUnusedFiles) {
|
||||
}
|
||||
|
||||
TEST(Construct, AddFunctions) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two functions.
|
||||
@ -287,11 +233,8 @@ TEST(Construct, AddFunctions) {
|
||||
|
||||
m.AddFunctions(vec.begin(), vec.end());
|
||||
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n"
|
||||
"FUNC 2987743d0b35b13f b369db048deb3010 938e556cb5a79988"
|
||||
" _and_void\n"
|
||||
@ -308,7 +251,7 @@ TEST(Construct, AddFunctions) {
|
||||
}
|
||||
|
||||
TEST(Construct, AddFrames) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// First STACK CFI entry, with no initial rules or deltas.
|
||||
@ -342,11 +285,8 @@ TEST(Construct, AddFrames) {
|
||||
m.AddStackFrameEntry(entry3);
|
||||
|
||||
// Check that Write writes STACK CFI records properly.
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n"
|
||||
"STACK CFI INIT ddb5f41285aa7757 1486493370dc5073 \n"
|
||||
"STACK CFI INIT 8064f3af5e067e38 de2a5ee55509407"
|
||||
@ -411,7 +351,7 @@ TEST(Construct, UniqueFiles) {
|
||||
}
|
||||
|
||||
TEST(Construct, DuplicateFunctions) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two functions.
|
||||
@ -421,11 +361,8 @@ TEST(Construct, DuplicateFunctions) {
|
||||
m.AddFunction(function1);
|
||||
m.AddFunction(function2);
|
||||
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n"
|
||||
"FUNC d35402aac7a7ad5c 200b26e605f99071 f14ac4fed48c4a99"
|
||||
" _without_form\n",
|
||||
@ -433,7 +370,7 @@ TEST(Construct, DuplicateFunctions) {
|
||||
}
|
||||
|
||||
TEST(Construct, FunctionsWithSameAddress) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two functions.
|
||||
@ -443,11 +380,8 @@ TEST(Construct, FunctionsWithSameAddress) {
|
||||
m.AddFunction(function1);
|
||||
m.AddFunction(function2);
|
||||
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
EXPECT_STREQ("MODULE os-name architecture id-string name with spaces\n"
|
||||
"FUNC d35402aac7a7ad5c 200b26e605f99071 f14ac4fed48c4a99"
|
||||
" _and_void\n"
|
||||
@ -459,7 +393,7 @@ TEST(Construct, FunctionsWithSameAddress) {
|
||||
// Externs should be written out as PUBLIC records, sorted by
|
||||
// address.
|
||||
TEST(Construct, Externs) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two externs.
|
||||
@ -473,11 +407,8 @@ TEST(Construct, Externs) {
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(extern2);
|
||||
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
|
||||
EXPECT_STREQ("MODULE " MODULE_OS " " MODULE_ARCH " "
|
||||
MODULE_ID " " MODULE_NAME "\n"
|
||||
@ -489,7 +420,7 @@ TEST(Construct, Externs) {
|
||||
// Externs with the same address should only keep the first entry
|
||||
// added.
|
||||
TEST(Construct, DuplicateExterns) {
|
||||
FILE *f = checked_tmpfile();
|
||||
stringstream s;
|
||||
Module m(MODULE_NAME, MODULE_OS, MODULE_ARCH, MODULE_ID);
|
||||
|
||||
// Two externs.
|
||||
@ -503,11 +434,8 @@ TEST(Construct, DuplicateExterns) {
|
||||
m.AddExtern(extern1);
|
||||
m.AddExtern(extern2);
|
||||
|
||||
m.Write(f);
|
||||
checked_fflush(f);
|
||||
rewind(f);
|
||||
string contents = checked_read(f);
|
||||
checked_fclose(f);
|
||||
m.Write(s);
|
||||
string contents = s.str();
|
||||
|
||||
EXPECT_STREQ("MODULE " MODULE_OS " " MODULE_ARCH " "
|
||||
MODULE_ID " " MODULE_NAME "\n"
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <assert.h>
|
||||
#include <cxxabi.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -27,6 +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.
|
||||
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
@ -46,7 +47,7 @@ int main(int argc, char **argv) {
|
||||
if (argc == 3)
|
||||
debug_dir = argv[2];
|
||||
|
||||
if (!WriteSymbolFile(binary, debug_dir, stdout)) {
|
||||
if (!WriteSymbolFile(binary, debug_dir, std::cout)) {
|
||||
fprintf(stderr, "Failed to write symbol file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user