Debugging a weird 'file not found' error
jvns.ca
jvns.ca
As a software developer, it's bad enough that I have to constantly challenge my assumptions to try to debug something. It's even worse when I have to think "What part of the system is lying to me?"
Recently I upgraded Jira Service Desk (new name: Service Management) to a new version. After the upgrade I got an error in the administrative section of the app. It popped up an error message saying "Something is wrong. Contact administrator." Ugh... but I'm the administrator! And I don't know what's wrong.
Maybe it's not a misleading error message, but it's certainly an unhelpful one.
Since software can be a lying devil, I am happy that I can make use of so much open source software. I've often delved into the source code to understand what's going on. Once I do that I can either fix the issue, work around it, or even submit a PR upstream.
It's much harder to figure out what's going wrong with closed source software. And even if I do figure it out, I'm powerless to change the situation.
Now if you'll excuse me, I'll be contacting Atlassian support to help me fix the "Something is wrong" message.
If you ever used OS/2 you might have seen that, too.
The problem is, the original handbooks are hard to come by[1], and while another wonderful thing (in some aspects) is that a modern, current mainframe is still compatible with that old code from the 70s, and so you can use their resources, you sometimes encounter error codes where the description is basically just "contact IBM". Most likely because IBM really does not expect you to encounter that error on a mainframe delivered since, say, the Berlin Wall fell.
Even if I wanted to, I doubt IBM wants to sell me a support contract for their 4 decades old last public domain version of their mainframe OS on an emulator.
[1] ... and especially on that old OS the message itself can be cryptically short, likely because even a few bytes still cost serious money in the beginning. You still see that in bootloaders.
Actually, the source can lie since the compiler could be buggy. The disassembly won't, and if you see the equivalent of 1+1=3 then you know you probably have stumbled upon a hardware bug. A hex editor is your friend; you can spend hours or even days on trying to figure out how to compile the thing correctly with all its dependencies and not change something else, or a few seconds to minutes finding what bytes to change.
In fact, the chance of running into a compiler bug is so low at higher levels, that you're better off assuming that it is not a compiler or hardware bug. If it is, you're either doing something so special that you a) might want to be doing it differently if inadvertently, or b) know exactly what you're dealing with already. Or you encountered something worth bragging about, and finding such a corner case unicorn in assembly when you don't even know the original source to know what is supposed to happen, gosh...
Otherwise, it's a bit akin to expect from a car mechanic that they can run a debugger on the ECU, or doing chemical analysis on exhaust buildup...
That's precisely the problem. People not knowing the fundamentals --- because the industry doesn't like for it to be widely known that a lot of DRM and other user-hostility can be disabled with changing a single bit, for example --- thinking open-source is somehow a panacea, then either wondering why it's so difficult to fix what should really be a simple thing that is "open source", or spending a ton of time trying to do it anyway. Then there's the stuff that's "open source" but effectively "look but don't touch" because they are so massive or otherwise locked out in some other way. If you've tried to change something in Android or one of its apps, you might have experienced that. (Fortunately, Java decompiles very easily.)
In other words, RMS was only partially right about open-source. Don't let that be a distraction from the real issue of software freedom which is closer to "right to repair"/modify.
Otherwise, it's a bit akin to expect from a car mechanic that they can run a debugger on the ECU, or doing chemical analysis on exhaust buildup...
No, it's more like expecting that one will look for spark, fuel, and air on a no-start before blindly swapping parts, or indeed know how to tell whether the mixture is rich or lean from the deposits on the spark plugs...
There's also whole categories of user-hostile software that can't be fixed with a simple binary patch -- ASM can still be obfuscated, or DRM might be enforced by a secondary processor. Dealing with those at a technical level is what I would definitely consider above the basics.
Similarly, right to repair advocacy has focused on cultural and legislative changes rather than greater technical competence. It's a good and useful skill but addressing the ills of the software industry won't come from assembly knowledge.
...unless the disassembler is buggy.
> ENOENT The file pathname or a script or ELF interpreter does not exist.
(Well, I just knew the answer in this case because I've hit this particular error a bunch of times, but that's not really the point.)
I actually agree with jmgao's point that this inscrutability is the downside of syscalls' limited error return; just pointing out the good as well as the bad.
Just printing error ids is fine if there is a good knowledge base which addresses them.
This is a lot of fun to do when you're trying to figure out why your network driver is crashing. Had that happen before and had to result to using my mobile phone, trying to identically write the code from my computer. Again, lots of fun. Extra fun ensures when the solution involves having to download a new driver, which you then have to download on the phone, figure out how to transfer (good luck if you have an iPhone) without using WiFi since your network card doesn't work, and then finally apply the update.
Maybe, just maybe, they can add a "Details" button that would actually print out the error itself.
War and Peace is about three megabytes uncompressed.
So, even if you shipped all the translations, I doubt you'd have a full gigabyte's worth. Especially not if you compressed them while at rest.
That is like a separate project, just as collecting/authoring the messages initially would be.
Or maybe this part of Windows is English-only? Not sure. Still, it would be quite the project to manage only for English.
Not sure. If the error messages are translated on the website, they could as well put those translations directly into the OS. If they are not translated on the website, no need to translate them in the OS.
AFAIK, languages have to be separately installed/shipped with the installation, so if you have EN-US installed, you'll just have the error messages for EN-US.
As the other poster said, error codes made a lot of sense when bytes were expensive (you could fit 255 kinds of fail in a single one!). Bytes are no longer expensive. Brain time is more expensive now. Optimise for that – and be descriptive.
* and good developers aren't necessarily good writers
For example, the old "Error occurred, check the logs" is useful because it tells me that there is info in the logs.
It's even better if it says "Error 4824 occurred, check the logs" because it gives me an error code to try to look up.
Better yet is "Error 4824 occurred, check the foobar-error.log file for transaction_id nv3kd32dx9" which also gives you which error log to check AND gives you a unique ID to grep for. Sadly, I've NEVER seen that type of error message in any software.
Cheatsheet (standard issue hello world -- hello.c)
# gcc -o hello ./hello.c
# file ./hello ./hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=1e41ccb72d7a36ab5ac0c2c11bcd0e994e5eb013, not stripped
This bit is what throws that error if unavailable:
"interpreter /lib64/ld-linux-x86-64.so.2"
Also, if ldd / file are unavailable but readelf is:
# readelf -a ./hello | grep interpreter
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
# readelf -a ./hello | grep NEEDED0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
Anyway, I too found the "file not found" error to be terribly unhelpful the first time I encountered it half a decade go. I thought I was going crazy.
"$ ldd ./serve"
"not a dynamic executable"
No need to use ldd, can use readelf, as in step 4 $ readelf -d ./serve
There is no dynamic section in this file.
http://www.catonmat.net/blog/ldd-arbitrary-code-execution/"And statically linking in this case doesn't even produce a bigger binary (for some reason it seems to produce a slightly smaller binary?? I don't know why that is)"
To force use of built-in net libs instead of system ones, and remove debugging info to make the binary a little smaller, something like this
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -ldflags '-w -extldflags "-static"' -o serve *.go
re: "File not found" I encounter this "weird bug" all the time because I like to build smaller, self-contained, no dependency systems and started doing so long before Docker existed. Static binaries are one way to solve the missing linker problem. Chroots and the like are another. I will sometimes create an alias that runs the program using chroot. Then I can keep the chroot directory as small as possible, populating it with only those resources the program actually needs. This is praticularly fun when it is an older program, no source available, and requires older versions of glibc, and other libraries. Dynamic linking in a time of GBs of core memory and TBs of storage is something I still question. People say dynamic linking is useful for the library "version control" it allows, but this just becomes a PITA for someone wanting to use older programs. Lots of folks compiling static binaries today for their Github repos only do so with glibc, which is hostile toward static linking. For someone using musl, this is no better than a dynamically-linked binary.The number of times I’ve had to unravel a bug where we iterated on a string rather than the list containing strings…
Instead of causing an error by dividing by 0, JavaScript, for example, will return Infinity which corrupts any further calculations.
It can be caused by dynamic typing but it also creeps into any language that allows implicit type conversion. Java, for example, is statically typed but allows some implicit type conversion in primitive types.
The problem is that an implicit type conversion is easy for a programmer to miss, and thus they don't realize that that the variable doesn't have the value or type they're expecting.
I've seen this error often enough to know that if you're doing anything with execution, "file not found" = "the file, or one of its dependencies, was not found". On the other hand, "file not found" on an open() call when you seem to see that it exists --- which I have actually seen --- should take you down a different path of troubleshooting, such as making sure you're in the right path and on the right machine.
Many system calls (sysctl as just one example) happily return ENOENT when they did not find a thing no matter whether it is even remotely related to a file. I picked sysctl because it also can return ENOTDIR and EISDIR for something not related to directories at all.
It's commonly not that much of an issue when you handle the error in code, but if the error gets "helpfully" bubbled up with its strerror description (instead of just a number), it can sure confuse people.
Somewhat simplified: errnos were at some distant point in the past divined into existence, and system calls and system level C APIs had to make with what they got since then.
One of the more interesting debugging adventures I had.
#!/bin/bash^M
That is, a non-printing carriage return after "bash"
and running the shell script "helpfully" kept telling me: "/bin/bash: no such file or directory"
/bin/bash
: No such file or directory
(Not that that's more helpful to a user, but at least a trained eye would immediately get very suspicious.)...since there's more than 2 valid line ending configurations?
The "bad interpreter" being there or not might be OS-specific, though. Unless some bash versions use fork+execve vs. posix_spawn or similar and that makes a difference there. I haven't checked. Overall I can see how even some current stuff might give the confusing message...
All I can guess is that in terminal, when that particular version of bash and OS interacted, it didn't go back to the start of line, and the CR was unprintable, so it all ended up on the same line.
I'd expect to have seen something like what you describe with the line break, if it were a bare LF, but in my case, it wasa bare CR.
: No such file or directory
(And the rest still applies.)I have no idea who thought this was a good idea, but this needs to be fixed. Somehow.
$ echo > /nonexistent-dir/filename
zsh: no such file or directory: /nonexistent-dir/filename
$ echo > /nonexistent-dir/filename
bash: /nonexistent-dir/filename: No such file or directory
In the middle of a shell script or makefile recipe, this can be hard to spot, and it may take a while to realise why it’s actually failing—that it’s the directory that doesn’t exist, not the file (which of course it doesn’t exist, you’re trying to create it!). I recently hit a messy race condition related to this: https://github.com/alexforster/libbpf-sys/commit/e65b4962e7d... (described in the commit message).I’ve been wondering about filing bugs against zsh and bash to be a little bit cleverer about their error message in this specific case, to only mention the nonexistent directory rather than the full path.
When fooling around running linux binaries on FreeBSD, ktrace/kdump show the (FreeBSD) kernel looking for the rtld..
With a static binary, you can even start your Dockerfile with `from scratch`. Feels good to have 5kb docker images
There aren’t a lot of expertly-written in-the-weeds-tech blog posts, but this one was crafted so well. I guess that’s why Julia Evans’ name is so recognizable.