When debugging program crashes, memory misuse debuggers become handy. AddressSanitizer [1] is implemented by the compiler (both gcc and clang support it), where each memory access is first checked against some state information that indicate whether the address is safe to access (e.g. malloc'ed and not freed). The state information is maintained by some library called libasan.so or libsanitizer, and all programms compiled with address sanitizer support need to be linked (statically or dynamically) against it.
To get a program with address sanitizer support compiled in, it needs the following flags:
TARGET_CPPFLAGS += -fsanitize=address -fno-omit-frame-pointer
TARGET_CFLAGS += -fsanitize=address -fno-omit-frame-pointer
TARGET_LDFLAGS += -lasan -fsanitize=address
TARGET_LDFLAGS_C += -lasan -fsanitize=address
So the toolchain (gcc) needs to be compiled with support for compiling for address sanitizer, that is using gcc "--enable-sanitizer" configure argument. The trouble with OpenWRT is that it uses uClibc, so libsanitizer does not compile out of the box against it. Additionally, libasan.so needs to be packed (it depends on libstdcpp) and packets compiled with address sanitizer need to depend on libasan, thought that was not that much of an issue.
So how can one enable libsanitizer with uclibc? The following steps are for toolchain gcc 4.8, gcc 4.9 will be more difficult because its extra features result in more (conflicting) dependencies.
- patch gcc 4.8 to avoid using __libc_malloc as uClibc does not provide this [2]
- change OpenWRT toolchain Makefiles to enable sanitizer support for gcc
diff --git a/toolchain/gcc/final/Makefile b/toolchain/gcc/final/Makefile
index 3fb5ccf..4fae52f 100644
--- a/toolchain/gcc/final/Makefile
+++ b/toolchain/gcc/final/Makefile
@@ -4,7 +4,7 @@ include ../common.mk
GCC_CONFIGURE += \
--with-headers=$(TOOLCHAIN_DIR)/include \
- --disable-libsanitizer \
+ --enable-libsanitizer \
--enable-languages=$(TARGET_LANGUAGES) \
--enable-shared \
--enable-threads \
@@ -58,7 +58,7 @@ endef
define Host/Install
$(CleanupToolchain)
- $(_SINGLE)$(GCC_MAKE) -C $(GCC_BUILD_DIR) install
+ $(_SINGLE)$(GCC_MAKE) -C $(GCC_BUILD_DIR) install install-target-libsanitizer
# Set up the symlinks to enable lying about target name.
set -e; \
(cd $(TOOLCHAIN_DIR); \
diff --git a/toolchain/gcc/minimal/Makefile b/toolchain/gcc/minimal/Makefile
index 0344e1a..e14f64a 100644
--- a/toolchain/gcc/minimal/Makefile
+++ b/toolchain/gcc/minimal/Makefile
@@ -6,7 +6,7 @@ GCC_CONFIGURE += \
--with-newlib \
--without-headers \
--enable-languages=c \
- --disable-libsanitizer \
+ --enable-libsanitizer \
--disable-libssp \
--disable-shared \
--disable-threads
@@ -30,11 +30,11 @@ define Host/Prepare
endef
define Host/Compile
- +$(GCC_MAKE) $(HOST_JOBS) -C $(GCC_BUILD_DIR) all-gcc all-target-libgcc
+ +$(GCC_MAKE) $(HOST_JOBS) -C $(GCC_BUILD_DIR) all-gcc all-target-libgcc all-target-libsanitizer
endef
define Host/Install
- $(GCC_MAKE) -C $(GCC_BUILD_DIR) install-gcc install-target-libgcc
+ $(GCC_MAKE) -C $(GCC_BUILD_DIR) install-gcc install-target-libgcc install-target-libsanitizer
endef
define Host/Clean
- fix uClibc to pass the sixth syscall argument to the kernel on powerpc (as my platform is powerpc) (or you will get "failed to mmap" errors)
--- uClibc-0.9.33.2/libc/sysdeps/linux/powerpc/syscall.S 2015-05-14 09:24:29.299815401 +0200
+++ uClibc-0.9.33.2/libc/sysdeps/linux/powerpc/syscall.S 2015-05-14 09:24:41.187991584 +0200
@@ -30,6 +30,7 @@
mr 5,6
mr 6,7
mr 7,8
+ mr 8,9
sc
bnslr;
- add libasan to OpenWRT packages
diff --git a/package/libs/toolchain/Makefile b/package/libs/toolchain/Makefile
index 42b9935..aad3d3c 100644
--- a/package/libs/toolchain/Makefile
+++ b/package/libs/toolchain/Makefile
@@ -133,6 +133,34 @@ define Package/libstdcpp/config
endef
+define Package/libasan
+$(call Package/gcc/Default)
+ NAME:=libasan
+ TITLE:=GNU Standard ASAN Library v3
+ DEPENDS=+libstdcpp
+endef
+
+define Package/libasan/config
+ menu "Configuration"
+ depends on EXTERNAL_TOOLCHAIN && PACKAGE_libasan
+
+ config LIBASAN_ROOT_DIR
+ string
+ prompt "libasan shared library base directory"
+ depends on EXTERNAL_TOOLCHAIN && PACKAGE_libasan
+ default TOOLCHAIN_ROOT if !NATIVE_TOOLCHAIN
+ default "/" if NATIVE_TOOLCHAIN
+
+ config LIBASAN_FILE_SPEC
+ string
+ prompt "libasan shared library files (use wildcards)"
+ depends on EXTERNAL_TOOLCHAIN && PACKAGE_libasan
+ default "./lib/libasan.so.*"
+
+ endmenu
+endef
+
+
define Package/libc/Default
SECTION:=libs
CATEGORY:=Base system
@@ -420,6 +448,11 @@ ifeq ($(CONFIG_EXTERNAL_TOOLCHAIN),)
$(CP) $(TOOLCHAIN_DIR)/lib/libstdc++.so. $(1)/usr/lib/
endef
+ define Package/libasan/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(CP) $(TOOLCHAIN_DIR)/lib/libasan.so. $(1)/usr/lib/
+ endef
+
use_libutil=$(if $(CONFIG_EGLIBC_OPTION_EGLIBC_UTMP),libutil)
use_libnsl=$(if $(CONFIG_EGLIBC_OPTION_EGLIBC_NIS),libnsl)
use_nsswitch=$(if $(CONFIG_EGLIBC_OPTION_EGLIBC_NSSWITCH),libnss_dns libnss_files)
@@ -568,6 +601,15 @@ else
exit 0
endef
+ define Package/libasan/install
+ for file in $(call qstrip,$(CONFIG_LIBASAN_FILE_SPEC)); do \
+ dir=`dirname $$$$file` ; \
+ $(INSTALL_DIR) $(1)/$$$$dir ; \
+ $(CP) $(call qstrip,$(CONFIG_LIBASAN_ROOT_DIR))/$$$$file $(1)/$$$$dir/ ; \
+ done ; \
+ exit 0
+ endef
+
define Package/libstdcpp/install
for file in $(call qstrip,$(CONFIG_LIBSTDCPP_FILE_SPEC)); do \
dir=`dirname $$$$file` ; \
@@ -638,6 +680,7 @@ $(eval $(call BuildPackage,libgcc))
$(eval $(call BuildPackage,libatomic))
$(eval $(call BuildPackage,libssp))
$(eval $(call BuildPackage,libstdcpp))
+$(eval $(call BuildPackage,libasan))
$(eval $(call BuildPackage,libpthread))
$(eval $(call BuildPackage,libthread-db))
$(eval $(call BuildPackage,librt))
- fix malloc being called from within malloc replacement function indirectly due to stack unwinding needing it (similar to [3], but that patch does apply to gcc 4.8 ). This patch might lose backtraces when used with threaded applications.
--- gcc-linaro-4.8-2014.04/libsanitizer/sanitizer_common/sanitizer_linux.cc.orig 2015-05-15 09:50:10.190769407 +0200
+++ gcc-linaro-4.8-2014.04/libsanitizer/sanitizer_common/sanitizer_linux.cc 2015-05-15 09:50:52.503376258 +0200
@@ -494,9 +494,13 @@
}
void StackTrace::SlowUnwindStack(uptr pc, uptr max_depth) {
+ static int nested = 0;
this->size = 0;
+ if (nested)
+ max_depth = 0;
this->max_size = max_depth;
if (max_depth > 1) {
+ nested++;
_Unwind_Backtrace(Unwind_Trace, this);
// We need to pop a few frames so that pc is on top.
// trace[0] belongs to the current function so we always pop it.
@@ -507,6 +511,7 @@
else if (size > 4 && MatchPc(pc, trace[4])) to_pop = 4;
else if (size > 5 && MatchPc(pc, trace[5])) to_pop = 5;
this->PopStackFrames(to_pop);
+ nested--;
}
this->trace[0] = pc;
}
- fix register usage in asan (or you'll see all kinds of false-positive errors): [3] (the changelog part is not required)
- recompile OpenWRT from the scratch or atleast
make toolchain/gcc/final/clean
make toolchain/gcc/final/install
make toolchain/uClibc/clean
make toolchain/uClibc/install
- change and possibly recompile the packages you need address sanitizer support with (mind the libraries!)
When recompiling uClibc, make sure that all copies of libuClibc-0.9.33.2.so got updated, or you might end up with a copy that does not have the syscall function fixed. If not fixed, when stracing, you will see the last argument of mmap2 to be 0xffffffff or alike; and program startup will fail with "mmap failed".
With this, I finally got hostapd on an embedded device running and found the memory corruption bug I introduced by accident.
[1]
http://en.wikipedia.org/wiki/AddressSanitizer
[2]
http://patchwork.ozlabs.org/patch/233933/
[3]
https://github.com/gcc-mirror/gcc/commit/a15fa55a4a13bde63a86422bba672b3af8232a31
Trackbacks