The NumPy min and max trap

30.08.2016 20:21

There's an interesting trap that I managed to fall into a few times when doing calculations with Python. NumPy provides several functions with the same name as functions built into Python. These replacements typically provide a better integration with array types. Among them are the min() and max(). In vast majority of cases, NumPy versions are a drop-in replacement for built-ins. In a few, however, they can cause some very hard-to-spot bugs. Consider the following:

import numpy as np

print(max(-1, 0))
print(np.max(-1, 0))

This prints (at least in NumPy 1.11.1 and earlier):

0
-1

Where is the catch? The built-in max() can be used in two distinct ways: you can either pass it an iterable as the single argument (in which case the largest element of the iterable will be returned), or you can pass multiple arguments (in which case the largest argument will be returned). In NumPy, max() is an alias for amax() and that only supports the former convention. The second argument in the example above is interpreted as array axis along which to perform the maximum. It appears that NumPy thinks axis zero is a reasonable choice for a zero-dimensional input and doesn't complain.

Yes, recent versions of NumPy will complain if you have anything else than 0 or -1 in the axis argument. Having max(x, 0) in code is not that unusual though. I use it a lot as a shorthand when I need to clip negative values to 0. When moving code around between scripts that use NumPy, those that don't and IPython Notebooks (which do "from numpy import *" by default), its easy to mess things up.

I guess both sides are to blame here. I find that flexible functions that interpret arguments in multiple ways are usually bad practice and I try to leave them out of interfaces I design. Yes, they are convenient, but they also often lead to bugs. On the other hand, I would also expect NumPy to complain about the nonsensical axis argument. Axis -1 makes sense for a zero-dimensional input, axis 0 doesn't. The alias from max() to amax() is dangerous (and as far as I can see undocumented). A possible way to prevent such mistakes would be to support only the named version of the axis argument.

Posted by Tomaž | Categories: Code | Comments »

Linux loader for DOS-like .com files

25.08.2016 18:09

Back in the olden days of DOS, there was a thing called a .com executable. They were obsolete even then, being basically inherited from CP/M and replaced by DOS MZ executables. Compared to modern binary formats like ELF, .com files were exceedingly simple. There was no file header, no metadata, no division between code and data sections. The operating system would load the entire file into a 16-bit memory segment at offset 0x100, set the stack and the program segment prefix and jump to the first instruction. It was more of a convention than a file format.

While this simplicity created many limitations, it also gave rise to many fun little hacks. You could write a binary executable directly in a text editor. The absence of headers meant that you could make extremely small programs. A minimal executable in Linux these days is somewhere in the vicinity of 10 kB. With some clever hacking you might get it down to a hundred bytes or so. A small .com executable can be on the order of bytes.

A comment on Hacker News recently caught my attention. One could write a .com loader for Linux pretty easily. How easily would that be? I had to try it out.

/* Map address space from 0x00000 to 0x10000. */

void *p = mmap(	(void*)0x00000, 0x10000, 
		PROT_EXEC|PROT_READ|PROT_WRITE,
		MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,
		-1, 0);

/* Load file at address 0x100. */

(a boring loop here reading from a file to memory.)

/* Set stack pointer to end of allocated area, jump to 0x100. */

asm(
	"mov    $0x10000, %rsp\n"
	"jmp    0x100\n"
);

This is the gist of it for Linux on x86_64. First we have to map 64 kB of memory at the bottom of our virtual address space. This is where NULL pointers and other such beasts roam, so it's unallocated by default on Linux. In fact, as a security feature the kernel will actively prevent such calls to mmap(). We have to disable this protection first with:

$ sysctl vm.mmap_min_addr=0

Memory mappings have to be aligned with page boundaries, so we can't simply map the file at address 0x100. To work around this we use an anonymous map starting at address 0 and then fill it in manually with the contents of the .com file at the correct offset. Since we have the whole 64 bit linear address space to play with, choosing 0x100 is a bit silly (code section usually lives somewhere around 0x400000 on Linux), but then again, so is this entire exercise.

Once we have everything set up, we set the stack pointer to the top of our 64 kB and jump to the code using some in-line assembly. If we were being pedantic, we could set up the PSP as well. Most of it doesn't make much sense on Linux though (except for command-line parameters maybe). I didn't bother.

Now that we have the loader ready, how do we create a .com binary that will work with it? We have to turn to assembly:

    call   greet
    mov    rax, 60
    mov    rdi, 0
    syscall

# A function call, just to show off our working stack.
greet:
    mov     rax, 1
    mov     rdi, 1
    mov     rsi, msg
    mov     rdx, 14
    syscall
    ret

    msg db      "Hello, World!", 10

This will compile nicely into an x86_64 ELF object file with NASM. We then have to link it into an ELF executable using a custom linker script that tells the linker that all sections will be placed in a chunk of memory starting at 0x100:

MEMORY
{
	RAM (rwx) : ORIGIN = 0x0100, LENGTH = 0xff00
}

Linker will create an executable which contains our code with all the proper offsets, but still has all the ELF cruft around it (it will segfault nicely if you try to run it with kernel's default ELF loader). As the final step, we must dump its contents into a bare binary file using objcopy:

$ objcopy -S --output-target=binary hello.elf hello.com

Finally, we can run our .com file with our loader:

$ ./loader hello.com
Hello, World!

As an extra convenience, we can register our new loader with the kernel, so that it will be invoked each time you try to execute a file with the .com extension (update-binfmts is part of the binfmt-support package on Debian):

$ update-binfmts --install com /path/to/loader --extension com
$ ./hello.com
Hello, World!

And there you have it, a nice 59 byte "Hello, World" binary for Linux. If you want to play with it yourself, see the complete example on GitHub that has a Makefile for your convenience. If you make something small and fun with it, please drop me a note.

One more thing. In case it's not clear at this point, this loader will not work with DOS executables (or CP/M in that case). Those expect to be run in 16-bit real mode and rely on DOS services. Needless to say, my loader makes no attempts to provide those. Code will run in the same environment as other Linux user space processes (albeit at a weird address) and must use the usual kernel syscalls. If you want to run old DOS stuff, use DOSBox or something similar.

Posted by Tomaž | Categories: Code | Comments »

A thousand pages of bullet journal

12.08.2016 17:27

A few weeks ago I filled the 1000th page in my Bullet Journal. Actually, I don't think I can call it that. It's not in fact that much oriented around bullet points. It's just a series of notebooks with consistently numbered, dated and titled pages for easy referencing, monthly indexes and easy-to-see square bullet points for denoting tasks. Most of the things I said two years ago still hold, so I'll try not to repeat myself too much here.

A thousand pages worth of notebooks.

Almost everything I do these days goes into this journal. Lab notes, sketches of ideas, random thoughts, doodles of happy ponies, to-do lists, pages worth of crossed-out mathematical derivations, interesting quotes, meeting notes and so on. Writing things down in a notebook often significantly clears them up. Once I have a concise and articulated definition of a problem, the solution usually isn't far away. Pen and paper helps me keep focus at talks and meetings, much like an open laptop does the opposite.

Going back through past notes gives a good perspective on how much new ideas depend on the context and mindset that created them. An idea for some random side-project that seems interesting and fun at first invariably looks much less shiny and worthy of attention after reading through the written version a few days or weeks later. I can't decide though whether it's better to leave such a thing on paper or hack together some half-baked prototype before the initial enthusiasm fades away.

The number of pages I write per month appears to be increasing. That might be because I settled on using cheap school notebooks. I find that I'm much more relaxed scribbling into a 50 cent notebook than ruining a 20€ Moleskine. Leaving lots of whitespace is wasteful, but helps a lot with readability and later corrections. Yes, whipping out a colorful children's notebook at an important meeting doesn't look very professional. Then again, most people at such meetings are too busy typing emails into their laptops to notice.

Number of pages written per month.

As much as it might look like a waste of time, I grew to like the monthly ritual of making an index page. I like the sense of achievement it gives me when I look back at what I've accomplished the previous month. It's also an opportunity for reflection. If the index gets hard to put all on one page, that's a good sign that the previous month was all too fragmented and that too many things wanted to happen at once.

The physical nature of the journal means that I can't carry the whole history with me at all times. That is also sometimes a problem. It is an unfortunate feature of my line of work that it is not uncommon for people to want to have unannounced meetings about a topic that was last discussed half a year ago. On the other hand, saying that I don't have my notes on me at that moment does present an honest excuse.

Indexes help, but finding things can be problematic. Then again, digital content (that's not publicly on the web) often isn't much better. I commonly find myself frustratingly searching for some piece of code or a document I know exists somewhere on my hard disk but can't remember any exact keyword that would help me find it. I considered making a digital version of monthly indexes at one point. I don't think it would be worth the effort and it would destroy some of the off-line quality of the notebook.

As I mentioned previously, gratuitous cross-referencing between notebook pages, IPython notebooks and other things does help. I tend not to copy tasks between pages, like in the original Bullet Journal idea. For projects that are primarily electronics related though, I'm used to keeping a separate folder with all the calculations and schematics, a habit I picked up long ago. There are not many such projects these days, but I did on one occasion photocopy pages from the notebook. I admit that made me feel absolutely archaic.

Posted by Tomaž | Categories: Life | Comments »

Oxidized diodes

08.08.2016 20:41

Out of curiosity, I salvaged these three LEDs from an old bicycle light that stopped working. Rain must have gotten inside it at one point, because the copper on the PCB was pretty much eaten away by the electrolysis. The chip that blinked the light was dead, but LEDs themselves still work.

Three LEDs damaged by water compared to a new one.

It's interesting how far inside the bulb the steel leads managed to oxidize. You can see that the right hand lead (anode) on all LEDs is brown all the way to the top, where a bond wire connects it to the die. I would have thought that the epoxy makes a better seal. For comparison, there's a similar new LED on the right. It's also interesting that the positive terminal was more damaged than the negative. On the right-most LED the terminal was actually eaten completely through just outside the bulb (although poking the remains with a needle still lights up the LED, so obviously the die itself was not damaged).

Posted by Tomaž | Categories: Life | Comments »