├── .gitignore ├── generated ├── btry ├── btry.hex └── readelf.txt ├── .editorconfig ├── makefile ├── readme.md └── btry.s /.gitignore: -------------------------------------------------------------------------------- 1 | btry 2 | btry.o 3 | -------------------------------------------------------------------------------- /generated/btry: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meribold/btry/HEAD/generated/btry -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf_8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 90 10 | 11 | [.git/COMMIT_EDITMSG] 12 | max_line_length = 72 13 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | btry: btry.o 2 | objcopy -O binary $< $@ 3 | chmod +x $@ 4 | 5 | btry.o: btry.s 6 | as -mx86-used-note=no $< -o $@ 7 | 8 | .PHONY: clean 9 | clean: 10 | rm -f btry.o btry 11 | 12 | .PHONY: strace 13 | strace: btry 14 | strace ./btry >/dev/null 15 | 16 | generated/btry: btry 17 | cp $< $@ 18 | 19 | generated/btry.hex: generated/btry 20 | hexdump -ve '16/1 "%02x " "\n"' $< | sed 's/ \+$$//' > $@ 21 | 22 | generated/readelf.txt: generated/btry 23 | readelf -a $< | sed 's/ \+$$//' > $@ 24 | -------------------------------------------------------------------------------- /generated/btry.hex: -------------------------------------------------------------------------------- 1 | 7f 45 4c 46 02 01 01 00 41 ba 33 00 01 00 eb 18 2 | 02 00 3e 00 01 00 00 00 08 00 01 00 00 00 00 00 3 | 38 00 00 00 00 00 00 00 41 b5 0a 41 bc 20 57 68 4 | 20 eb 35 25 29 0a 38 00 01 00 00 00 07 00 00 00 5 | 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 6 | 0f 05 b0 3c 31 ff 0f 05 80 01 00 00 00 00 00 00 7 | 00 02 00 00 00 00 00 00 e8 53 00 00 00 45 89 f7 8 | e8 4b 00 00 00 31 d2 44 89 f0 48 6b c0 64 49 f7 9 | f7 e8 bf 00 00 00 41 c6 42 ff 28 49 83 ea 05 45 10 | 89 22 41 97 e8 7c 00 00 00 66 41 c7 42 fe 2f 20 11 | 49 83 ea 06 45 89 22 41 96 e8 67 00 00 00 6a 01 12 | 58 89 c7 44 89 d6 ba 36 00 01 00 44 29 d2 eb 90 13 | bf 58 01 01 00 6a 02 58 31 f6 0f 05 85 c0 78 33 14 | c7 47 24 6e 6f 77 00 97 96 48 8d 74 24 f7 6a 09 15 | 5a 0f 05 fe c8 45 31 f6 31 c9 31 d2 4d 6b f6 0a 16 | 8a 54 0c f7 80 ea 30 49 01 d6 48 ff c1 48 39 c8 17 | 75 ea c3 66 41 bc 20 41 c7 47 1d 63 68 61 72 c6 18 | 47 22 65 eb ab 31 d2 41 b9 40 42 0f 00 41 f7 f1 19 | 41 90 92 31 d2 41 b9 a0 86 01 00 41 f7 f1 41 f6 20 | f5 80 c4 30 49 ff ca 88 e0 41 88 02 49 ff ca 41 21 | c6 02 2e 41 90 31 d2 41 f7 f5 80 c2 30 49 ff ca 22 | 41 88 12 85 c0 75 ee c3 2f 73 79 73 2f 63 6c 61 23 | 73 73 2f 70 6f 77 65 72 5f 73 75 70 70 6c 79 2f 24 | 42 41 54 30 2f 65 6e 65 72 67 79 5f 66 75 6c 6c 25 | -------------------------------------------------------------------------------- /generated/readelf.txt: -------------------------------------------------------------------------------- 1 | ELF Header: 2 | Magic: 7f 45 4c 46 02 01 01 00 41 ba 33 00 01 00 eb 18 3 | Class: ELF64 4 | Data: 2's complement, little endian 5 | Version: 1 (current) 6 | OS/ABI: UNIX - System V 7 | ABI Version: 65 8 | Type: EXEC (Executable file) 9 | Machine: Advanced Micro Devices X86-64 10 | Version: 0x1 11 | Entry point address: 0x10008 12 | Start of program headers: 56 (bytes into file) 13 | Start of section headers: 7518514095835493697 (bytes into file) 14 | Flags: 0x2535eb20 15 | Size of this header: 2601 (bytes) 16 | Size of program headers: 56 (bytes) 17 | Number of program headers: 1 18 | Size of section headers: 0 (bytes) 19 | Number of section headers: 7 20 | Section header string table index: 0 21 | 22 | Program Headers: 23 | Type Offset VirtAddr PhysAddr 24 | FileSiz MemSiz Flags Align 25 | LOAD 0x0000000000000000 0x0000000000010000 0x050fff313cb0050f 26 | 0x0000000000000180 0x0000000000000200 RWE 0xf7894500000053e8 27 | 28 | There is no dynamic section in this file. 29 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | A battery status program for x86-64 Linux laptops in the form of a 384-byte ELF 2 | executable. 3 | 4 | ## Usage 5 | 6 | $ btry 7 | 30.6 Wh / 31.1 Wh (98%) 8 | 9 | Sometimes there are no `energy_now` and `energy_full` files, but `charge_*` files instead 10 | (at least on my ThinkPad X220). If this is the case, `btry` prints ampere hours instead 11 | of watt hours. 12 | 13 | $ btry 14 | 2.2 Ah / 2.8 Ah (78%) 15 | 16 | ## Installation 17 | 18 | ``` 19 | base64 -d < btry && chmod +x btry 20 | 4AF/AUIAAD+R1tX9PhIql6sjxkjsaokIKBWGZx/84juTwnC0Z1zPT1NrX7p18E3d+SYZAoDxhoI6EZaJVYtZPl3R 21 | Q0GmvBz3Jpv7zJ0Mejc8xhxNzv48cldvmf1G7JlbiLQqwNU38t9FmUN0yd7iBvOy1V9kLQfYAQnRBknAjvvoYTTG 22 | 30UkkfizhtW6tCsfsG3fEsm3MvG41qzqCrQjm8NMwkiQyGpf/o3Uzf4jIctfUBEL19LAmr9t0cVJTrUCVYy0GW9c 23 | vPd/2izGOtFw/14myD8lseTo0EpXtChauuNLZwgWlTynXkSAK7mP5HXtIyTA7QEWZ7glaptCYfF5ZE38eR+BYZ4B 24 | bMHK3oYqcY0rsWWjSVvpMZpghUEnzQw6cSYuHN5nj0BpdKR/zHyp942jAts/FTHT6GDTu5o15fn8PQP76YOG9UQA 25 | EOF 26 | ``` 27 | 28 | ## Limitations 29 | 30 | * Anything that's not x86-64 and Linux is definitely not supported. 31 | * I don't know how standard/portable the `/sys/class/power_supply/BAT0` path used 32 | actually is. 33 | * If neither an `energy_full` nor a `charge_full` file exists in 34 | `/sys/class/power_supply/BAT0`, an infinite loop results. 35 | * Extra batteries (like in the ThinkPad T480) are ignored. 36 | 37 | ## Build instructions 38 | 39 | ### Linux on x86-64 40 | 41 | make 42 | 43 | ### Other platforms 44 | 45 | No. 46 | 47 | ## Notes 48 | 49 | When my ThinkPad X220 is plugged in at the time I wake it from suspend mode, I get the 50 | `charge_now` file. When it is not plugged in I get the `energy_now` file. At least I 51 | think that's how it works. 52 | -------------------------------------------------------------------------------- /btry.s: -------------------------------------------------------------------------------- 1 | .byte 0x7f 2 | .ascii "ELF" 3 | .byte 2, 1, 1, 0 4 | mov $0x10033, %r10d 5 | jmp plus28 6 | .short 2, 0x3e 7 | .long 1 8 | .quad 0x10008, 0x38 9 | plus28: 10 | mov $10, %r13b 11 | mov $0x20685720, %r12d # " Wh " 12 | jmp plus68 13 | .ascii "%)\n" 14 | .short 0x38 15 | 16 | .long 1, 7 17 | .quad 0, 0x10000 18 | plus50: 19 | syscall 20 | mov $60, %al # system call 60 is exit 21 | xor %edi, %edi # we want return code 0 22 | syscall # invoke operating system to exit 23 | .quad 0x180, 0x200 24 | 25 | plus68: 26 | # read the contents of the file specified by the path at $path into %r14 27 | call get_number 28 | 29 | # copy the result of get_number (the energy_full or charge_full value) and read the 30 | # energy_now or charge_now file (store the contents in %r14) 31 | mov %r14d, %r15d 32 | 33 | call get_number 34 | 35 | # calculate the remaining energy as a percentage 36 | xor %edx, %edx 37 | mov %r14d, %eax 38 | imul $100, %rax 39 | div %r15 40 | call add_eax_to_output_string 41 | 42 | # prepend "(" and then " Wh " (or " Ah ") to the output string 43 | movb $'(, -1(%r10) 44 | sub $5, %r10 45 | movl %r12d, (%r10) 46 | 47 | # prepend the energy_full (or charge_full) value to the output string 48 | xchg %r15d, %eax 49 | call add_eax_to_output_string_as_decimal 50 | 51 | # prepend "/ " and then " Wh " (or " Ah ") to the output string 52 | movw $0x202f, -2(%r10) 53 | sub $6, %r10 54 | movl %r12d, (%r10) 55 | 56 | # prepend the energy_now (or charge_now) value to the output string 57 | xchg %r14d, %eax 58 | call add_eax_to_output_string_as_decimal 59 | 60 | # write(1, %r10, $0x10036 - %r10) 61 | push $1 # system call 1 is write 62 | pop %rax 63 | mov %eax, %edi # file handle 1 is stdout 64 | mov %r10d, %esi # address of string to output 65 | mov $0x10036, %edx 66 | sub %r10d, %edx 67 | jmp plus50 68 | 69 | # read the file specified via %rdi; convert the contents to an integer stored in %r14 70 | get_number: 71 | # e.g. open("/sys/class/power_supply/BAT0/energy_now", O_RDONLY) 72 | mov $0x10158, %edi 73 | push $2 74 | pop %rax # system call 2 is open 75 | xor %esi, %esi # 0 means read-only 76 | syscall 77 | 78 | # sometimes there are no energy_* files but charge_* files instead 79 | test %eax, %eax 80 | js charge 81 | 82 | # change the path to "/sys/class/power_supply/BAT0/energy_now" (or "charge_now") 83 | movl $0x00776f6e, 36(%rdi) # "now\0" 84 | 85 | # read(fd, buffer, 9) 86 | xchg %eax, %edi # open returns a file descriptor in %rax; read expects it in %rdi 87 | xchg %esi, %eax # system call 0 is read 88 | lea -9(%rsp), %rsi # save file contents read from fd on the stack 89 | push $9 90 | pop %rdx # read up to 9 bytes 91 | syscall # the number of bytes read goes into %rax 92 | 93 | # convert file contents to an integer (stored in %r14) 94 | dec %al # subtract 1 so we don't process the newline 95 | xor %r14d, %r14d 96 | xor %ecx, %ecx 97 | xor %edx, %edx 98 | next_char: 99 | imul $10, %r14 100 | mov -9(%rsp, %rcx), %dl # load one character/byte/digit 101 | sub $'0, %dl # convert the character from ASCII 102 | add %rdx, %r14 # add this digit 103 | inc %rcx 104 | cmp %rcx, %rax 105 | jne next_char 106 | ret 107 | charge: 108 | mov $0x4120, %r12w # " A" 109 | 110 | # change the path to "/sys/class/power_supply/BAT0/charge_full" 111 | movl $0x72616863, 29(%rdi) # "char" 112 | movb $'e, 34(%rdi) 113 | jmp get_number 114 | 115 | # prepend `%eax / 1000000` with one decimal place to the output string; invalidates %eax, 116 | # %edx, %r8d, and %r9d 117 | add_eax_to_output_string_as_decimal: 118 | xor %edx, %edx 119 | mov $1000000, %r9d # 4-byte divisor 120 | div %r9d # div stores the quotient in %eax 121 | xchg %eax, %r8d # copy the quotient to %r8d 122 | 123 | # compute one decimal place 124 | xchg %edx, %eax # the remainder is the new dividend 125 | xor %edx, %edx 126 | mov $100000, %r9d # 4-byte divisor 127 | div %r9d # div stores the quotient in %eax 128 | 129 | # convert %eax to text (just one character) 130 | div %r13b # quotient and remainder are stored in %al and %ah, respectively 131 | add $'0, %ah # convert the remainder to ASCII 132 | dec %r10 133 | mov %ah, %al 134 | movb %al, (%r10) 135 | 136 | dec %r10 137 | movb $'., (%r10) 138 | xchg %r8d, %eax 139 | 140 | # prepend %eax to the output string; invalidates %eax and %edx 141 | add_eax_to_output_string: 142 | xor %edx, %edx 143 | div %r13d # quotient and remainder are stored in %eax and %edx, respectively 144 | add $'0, %dl # convert the remainder to ASCII 145 | dec %r10 146 | mov %dl, (%r10) 147 | test %eax, %eax 148 | jne add_eax_to_output_string 149 | ret 150 | 151 | .ascii "/sys/class/power_supply/BAT0/energy_full" 152 | --------------------------------------------------------------------------------