;;; x86-lookup.el --- jump to x86 instruction documentation -*- lexical-binding: t; -*- ;; This is free and unencumbered software released into the public domain. ;; Author: Christopher Wellons ;; URL: https://github.com/skeeto/x86-lookup ;; Package-Version: 20180528.1635 ;; Version: 1.2.0 ;; Package-Requires: ((emacs "24.3") (cl-lib "0.3")) ;;; Commentary: ;; Requires the following: ;; * pdftotext command line program from Poppler ;; * Intel 64 and IA-32 Architecture Software Developer Manual PDF ;; http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html ;; Building the index specifically requires Poppler's pdftotext, not ;; just any PDF to text converter. It has a critical feature over the ;; others: conventional line feed characters (U+000C) are output ;; between pages, allowing precise tracking of page numbers. These are ;; the markers Emacs uses for `forward-page' and `backward-page'. ;; Your copy of the manual must contain the full instruction set ;; reference in a single PDF. Set `x86-lookup-pdf' to this file name. ;; Intel optionally offers the instruction set reference in two ;; separate volumes, but don't use that. ;; Choose a PDF viewer by setting `x86-lookup-browse-pdf-function'. If ;; you provide a custom function, your PDF viewer should support ;; linking to a specific page (e.g. not supported by xdg-open, ;; unfortunately). Otherwise there's no reason to use this package. ;; Once configured, the main entrypoint is `x86-lookup'. You may want ;; to bind this to a key. The interactive prompt will default to the ;; mnemonic under the point. Here's a suggestion: ;; (global-set-key (kbd "C-h x") #'x86-lookup) ;; This package pairs well with `nasm-mode'! ;;; Code (require 'cl-lib) (require 'doc-view) (defgroup x86-lookup () "Options for x86 instruction set lookup." :group 'extensions) (defcustom x86-lookup-pdf nil "Path to Intel's manual containing the instruction set reference." :group 'x86-lookup :type '(choice (const nil) (file :must-match t))) (defcustom x86-lookup-pdftotext-program "pdftotext" "Path to pdftotext, part of Popper." :group 'x86-lookup :type 'string) (defcustom x86-lookup-browse-pdf-function #'x86-lookup-browse-pdf-any "A function that launches a PDF viewer at a specific page. This function accepts two arguments: filename and page number." :group 'x86-lookup :type '(choice (function-item :tag "First suitable PDF reader" :value x86-lookup-browse-pdf-any) (function-item :tag "Evince" :value x86-lookup-browse-pdf-evince) (function-item :tag "Xpdf" :value x86-lookup-browse-pdf-xpdf) (function-item :tag "Okular" :value x86-lookup-browse-pdf-okular) (function-item :tag "gv" :value x86-lookup-browse-pdf-gv) (function-item :tag "zathura" :value x86-lookup-browse-pdf-zathura) (function-item :tag "MuPDF" :value x86-lookup-browse-pdf-mupdf) (function-item :tag "Sumatra PDF" :value x86-lookup-browse-pdf-sumatrapdf) (function-item :tag "browse-url" :value x86-lookup-browse-pdf-browser) (function :tag "Your own function"))) (defcustom x86-lookup-cache-directory (let ((base (or (getenv "XDG_CACHE_HOME") (getenv "LocalAppData") "~/.cache"))) (expand-file-name "x86-lookup" base)) "Directory where the PDF mnemonic index with be cached." :type 'string) (defvar x86-lookup-index nil "Alist mapping instructions to page numbers.") (defvar x86-lookup--expansions '(("^PREFETCH\\(h\\)$" "" "nta" "t0" "t1" "t2") ("^J\\(cc\\)$" "a" "ae" "b" "be" "c" "cxz" "e" "ecxz" "g" "ge" "l" "le" "na" "nae" "nb" "nbe" "nc" "ne" "ng" "nge" "nl" "nle" "no" "np" "ns" "nz" "o" "p" "pe" "po" "rcxz" "s" "z") ("^SET\\(cc\\)$" "a" "ae" "b" "be" "c" "e" "g" "ge" "l" "le" "na" "nae" "nb" "nbe" "nc" "ne" "ng" "nge" "nl" "nle" "no" "np" "ns" "nz" "o" "p" "pe" "po" "s" "z") ("^CMOV\\(cc\\)$" "a" "ae" "b" "be" "c" "e" "g" "ge" "l" "le" "na" "nae" "nb" "nbe" "nc" "ne" "ng" "nge" "nl" "nle" "no" "np" "ns" "nz" "o" "p" "pe" "po" "s" "z") ("^FCMOV\\(cc\\)$" "b" "e" "be" "u" "nb" "ne" "nbe" "nu") ("^LOOP\\(cc\\)$" "e" "ne") ("^VBROADCAST\\(\\)$" "" "ss" "sd" "f128") ("^VMASKMOV\\(\\)$" "" "ps" "pd") ("^VPBROADCAST\\(\\)$" "" "b" "w" "d" "q" "I128") ("^VPMASKMOV\\(\\)$" "" "d" "q") ("\\(\\)" ; fallback "match" "")) "How to expand mnemonics into multiple mnemonics.") (defun x86-lookup--expand (names page) "Expand string of PDF-sourced mnemonics into user-friendly mnemonics." (let ((case-fold-search nil) (rev-string-match-p (lambda (s re) (string-match re s)))) (save-match-data (cl-loop for mnemonic-raw in (split-string names " */ *") ;; Collapse "int 3" and "int n" into "int" for mnemonic = (replace-regexp-in-string " .+$" "" mnemonic-raw) for (_ . tails) = (cl-assoc mnemonic x86-lookup--expansions :test rev-string-match-p) nconc (cl-loop for tail in tails for rep = (replace-match tail nil nil mnemonic 1) collect (cons (downcase rep) page)))))) (cl-defun x86-lookup-create-index (&optional (pdf x86-lookup-pdf)) "Create an index alist from PDF mapping mnemonics to page numbers. This function requires the pdftotext command line program." (let ((mnemonic (concat "\\(?:.*\n\n?\\)?" "\\([[:alnum:]/[:blank:]]+\\)[[:blank:]]*" "\\(?:--\\|—\\)\\(?:.*\n\n?\\)\\{1,3\\}" "[[:blank:]]*Opcode")) (coding-system-for-read 'utf-8) (coding-system-for-write 'utf-8) (case-fold-search t)) (with-temp-buffer (call-process x86-lookup-pdftotext-program nil t nil (file-truename pdf) "-") (setf (point) (point-min)) (cl-loop for page upfrom 1 while (< (point) (point-max)) when (looking-at mnemonic) nconc (x86-lookup--expand (match-string 1) page) into index do (forward-page) finally (cl-return (cl-remove-duplicates index :key #'car :test #'string= :from-end t)))))) (defun x86-lookup--index-file (pdf) "Return index filename from PDF filename." (concat (sha1 pdf) "_v3")) (defun x86-lookup--save-index (pdf index) "Save INDEX for PDF in `x86-lookup-cache-directory'." (let* ((index-file (x86-lookup--index-file pdf)) (cache-path (expand-file-name index-file x86-lookup-cache-directory))) (mkdir x86-lookup-cache-directory t) (with-temp-file cache-path (prin1 index (current-buffer))) index)) (defun x86-lookup--load-index (pdf) "Return index PDF from `x86-lookup-cache-directory'." (let* ((index-file (x86-lookup--index-file pdf)) (cache-path (expand-file-name index-file x86-lookup-cache-directory))) (when (file-exists-p cache-path) (with-temp-buffer (insert-file-contents cache-path) (setf (point) (point-min)) (ignore-errors (read (current-buffer))))))) (defun x86-lookup-ensure-index () "Ensure the PDF index has been created, returning the index." (when (null x86-lookup-index) (cond ((null x86-lookup-pdf) (error "No PDF available. Set `x86-lookup-pdf'.")) ((not (file-exists-p x86-lookup-pdf)) (error "PDF not found. Check `x86-lookup-pdf'.")) ((setf x86-lookup-index (x86-lookup--load-index x86-lookup-pdf)) x86-lookup-index) ((progn (message "Generating mnemonic index ...") (setf x86-lookup-index (x86-lookup-create-index)) (x86-lookup--save-index x86-lookup-pdf x86-lookup-index))))) x86-lookup-index) (defun x86-lookup-ensure-and-update-index () "Ensure the PDF index has been created and (unconditionally) updated. Useful for forcibly syncing the index with the current PDF without resorting to manual deletion of index file on filesystem." (interactive) (cond ((null x86-lookup-pdf) (error "No PDF available. Set `x86-lookup-pdf'.")) ((not (file-exists-p x86-lookup-pdf)) (error "PDF not found. Check `x86-lookup-pdf'.")) ((message "Generating mnemonic index ...") (setf x86-lookup-index (x86-lookup-create-index)) (x86-lookup--save-index x86-lookup-pdf x86-lookup-index) (message "Finished generating mnemonic index.")))) (defun x86-lookup-browse-pdf (pdf page) "Launch a PDF viewer using `x86-lookup-browse-pdf-function'." (funcall x86-lookup-browse-pdf-function pdf page)) ;;;###autoload (defun x86-lookup (mnemonic) "Jump to the PDF documentation for MNEMONIC. Defaults to the mnemonic under point." (interactive (progn (x86-lookup-ensure-index) (let* ((mnemonics (mapcar #'car x86-lookup-index)) (thing (thing-at-point 'word)) (mnemonic (if (member thing mnemonics) thing nil)) (prompt (if mnemonic (format "Mnemonic (default %s): " mnemonic) "Mnemonic: "))) (list (completing-read prompt mnemonics nil t nil nil mnemonic))))) (let ((page (cdr (assoc mnemonic x86-lookup-index)))) (x86-lookup-browse-pdf (file-truename x86-lookup-pdf) page))) ;; PDF viewers: (defun x86-lookup-browse-pdf-pdf-tools (pdf page) "View PDF at PAGE using Emacs' `pdf-view-mode' and `display-buffer'." (require 'pdf-tools) (prog1 t (with-selected-window (display-buffer (find-file-noselect pdf :nowarn)) (with-no-warnings (pdf-view-goto-page page))))) (defun x86-lookup-browse-pdf-doc-view (pdf page) "View PDF at PAGE using Emacs' `doc-view-mode' and `display-buffer'." (prog1 t (unless (doc-view-mode-p 'pdf) (error "doc-view not available for PDF")) (with-selected-window (display-buffer (find-file-noselect pdf :nowarn)) (doc-view-goto-page page)))) (defun x86-lookup-browse-pdf-xpdf (pdf page) "View PDF at PAGE using xpdf." (start-process "xpdf" nil "xpdf" "--" pdf (format "%d" page))) (defun x86-lookup-browse-pdf-evince (pdf page) "View PDF at PAGE using Evince." (start-process "evince" nil "evince" "-p" (format "%d" page) "--" pdf)) (defun x86-lookup-browse-pdf-okular (pdf page) "View PDF at PAGE file using Okular." (start-process "okular" nil "okular" "-p" (format "%d" page) "--" pdf)) (defun x86-lookup-browse-pdf-gv (pdf page) "View PDF at PAGE using gv." (start-process "gv" nil "gv" "-nocenter" (format "-page=%d" page) "--" pdf)) (defun x86-lookup-browse-pdf-zathura (pdf page) "View PDF at PAGE using zathura." (start-process "zathura" nil "zathura" "-P" (format "%d" page) "--" pdf)) (defun x86-lookup-browse-pdf-sumatrapdf (pdf page) "View PDF at PAGE using Sumatra PDF." (start-process "sumatrapdf" nil "sumatrapdf" "-page" (format "%d" page) pdf)) (defun x86-lookup-browse-pdf-mupdf (pdf page) "View PDF at PAGE using MuPDF." ;; MuPDF doesn't have a consistent name across platforms. ;; Furthermore, Debian ships with a broken "mupdf" wrapper shell ;; script and must be avoided. Here we use `executable-find' to ;; avoid calling it as mupdf-x11 on non-X11 platforms. (let ((exe (or (executable-find "mupdf-x11") "mupdf"))) (start-process "mupdf" nil exe "--" pdf (format "%d" page)))) (defun x86-lookup-browse-pdf-browser (pdf page) "Visit PDF using `browse-url' with a fragment for the PAGE." (browse-url (format "file://%s#%d" pdf page))) (defun x86-lookup-browse-pdf-any (pdf page) "Try visiting PDF using the first viewer found." (or (ignore-errors (x86-lookup-browse-pdf-pdf-tools pdf page)) (ignore-errors (x86-lookup-browse-pdf-doc-view pdf page)) (ignore-errors (x86-lookup-browse-pdf-evince pdf page)) (ignore-errors (x86-lookup-browse-pdf-xpdf pdf page)) (ignore-errors (x86-lookup-browse-pdf-okular pdf page)) (ignore-errors (x86-lookup-browse-pdf-gv pdf page)) (ignore-errors (x86-lookup-browse-pdf-zathura pdf page)) (ignore-errors (x86-lookup-browse-pdf-mupdf pdf page)) (ignore-errors (x86-lookup-browse-pdf-sumatrapdf pdf page)) (ignore-errors (x86-lookup-browse-pdf-browser pdf page)) (error "Could not find a PDF viewer."))) (provide 'x86-lookup) ;;; x86-lookup.el ends here