(or emacs irrelevant)

elf-mode - view the symbol list in a binary

Recently, I've been looking at libigl. I didn't manage to fully figure out their CMake build system for tutorials: although each tutorial has a CMakeLists.txt, it's only possible to build them all at once.

So I decided to replace CMakeLists.txt with a good-old Makefile; how hard can it be? Concerning includes, not at all hard: the missing files are found with counsel-locate and added to the include path.

But I had some trouble matching a missing ld dependency to a library file. Fixed it with a bunch of googling and guesswork; I still wonder if there's a better way. But in the process, I've found this useful command:

readelf --syms libGL.so

which produces e.g.:

Symbol table '.dynsym' contains 2732 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 000000000004faf0     0 SECTION LOCAL  DEFAULT    8
     2: 00000000000e8f20     0 FUNC    GLOBAL DEFAULT   11 glGetIntegerui64i_vNV
     3: 00000000000e13e0     0 FUNC    GLOBAL DEFAULT   11 glGetMultiTexEnvfvEXT
     4: 00000000000d7440     0 FUNC    GLOBAL DEFAULT   11 glProgramUniform2uiv
     5: 00000000000cfdc0     0 FUNC    GLOBAL DEFAULT   11 glMultiTexCoord3sv

This is a pretty good representation of a binary file: in this example, instead of one megabyte of gibberish I see a bit more than 2732 lines describing the functions this file uses and provides.

Viewing the symbol list automatically

I liked the above representation so much that I want to see it by default. In Emacs, it's pretty easy to do with auto-mode-alist:

(add-to-list 'auto-mode-alist '("\\.\\(?:a\\|so\\)\\'" . elf-mode))

The above code instructs Emacs to call elf-mode function whenever the file name ends in *.a or *.so.

And here's the body of elf-mode:

(defvar-local elf-mode nil)

(defun elf-mode ()
  (let ((inhibit-read-only t))
    (if elf-mode
          (delete-region (point-min) (point-max))
          (insert-file-contents (buffer-file-name))
          (setq elf-mode nil))
      (setq elf-mode t)
      (delete-region (point-min) (point-max))
      (insert (shell-command-to-string
               (format "readelf --syms %s" (buffer-file-name)))))
    (set-buffer-modified-p nil)
    (read-only-mode 1)))

The idea is very simple: elf-mode is a toggle function that replaces the buffer contents with the shell command output. It carefully uses read-only-mode and set-buffer-modified-p so that the file will not be overwritten by accident with the symbol names.

Using autoload to avoid overhead

As you might imagine, looking at binaries isn't really a common task. Is it worth to be dragging this code around from now on, loading it on each start? The answer is yes, of course. Since the actual cost is negligible until the feature is used.

If you look above, elf-mode has an ;;;###autoload cookie before it. The cookie results in this line in my loaddefs.el:

(autoload 'elf-mode "modes/ora-elf" "" t nil)

My init.el always loads loaddefs.el, but never loads ora-elf.el where the function is defined. That file is only loaded when the function elf-mode is called for the first time. The above autoload statement simply instructs Emacs to load a particular file when elf-mode needs to be called.

When you use the package manager, the autoloads file is generated and loaded for you automatically:

$ tree elpa/ace-link-20160811.112/

├── ace-link-autoloads.el
├── ace-link.el
├── ace-link.elc
└── ace-link-pkg.el

0 directories, 4 files

Here, the package manager will always load ace-link-autoloads.el, which instructs Emacs to load ace-link.el when one of the ;;;###autoload functions is called and ace-link.el isn't yet loaded.

As an example of how useful delayed loading is: my 6000 line config starts in 1.8 seconds. About 40% of that time is spent on (package-initialize), which I assume is the package manager loading all those *-autoloads.el files that I have in my elpa/.


Let me know if there's interest to have elf-mode on MELPA. Also, if anyone knows how to set mode automatically based on the first few chars of the file (all binaries seem to start with ^?ELF), I'd like to know that as well. Happy hacking!