I’ve written more than my fair share of IP stacks, usually for high speed packet capture.
On one of these, every now and again, the stats would show that we were capturing at a line rate of terabits per second (or more), on devices with 100Mb Ethernet.
I tore my hair out trying to reproduce the problem. Turned out every now and again a corrupted IP packet would come through with a header length field that was impossibly small. We’d drop the packet as corrupt and update the “invalid packets” counter, but we also updated the “mbps” counter to show that we still processed that amount of data (i.e. we handled it just fine, we weren’t overloaded, it’s the packet that was wrong), but we used the computed packet length to add to the stats…
My favourite network bug (which, fortunately, I didn’t have to debug) was on our CHERI MIPS prototype. The CPU allowed loads to execute in speculation because loads never have side effects. It turns out that they do if they’re loading from a memory-mapped device FIFO. Sometimes, in speculation, the CPU would load 32 bits of data from the FIFO. The network stack would then detect the checksum mismatch and drop the packet. The sender would then adapt to higher packet loss and slow down. The user just saw very slow network traffic. Once this was understood, the CPU bug was fixed (only loads of cached memory are allowed in speculation). This was made extra fun by branch predictor aliasing, which meant that often the load would happen in speculation on a totally unrelated bit of code (and not the same bit)l
Apparently the Xbox 360 had a special uncached load instruction with a similar bug and the only way to avoid problems was to not have that instruction anywhere in executable memory.
The “fake threads” discussed here remind me of the Virtual Threads from Java’s Project Loom.
They also sound very much like Green Threads. Rust originally started out with Green Threads and then eventually dropped them and decided to support only Native Threads. Bringing them back now might face a difficult path.
What does “RL:” stand for? The program is called rusage so if there was going to be any prefix on these lines I would have expected “RU:” or maybe “rusage:”.
I tried to see if there were any hints in the git history. This prefix was added in https://github.com/jart/cosmopolitan/commit/cc1920749eb81f346badaf55fbf79620cb718a55. This commit touches over a thousand files. The description of this commit talks about TLS and makes no mention of rusage. It appears that a fairly large rewrite of rusage somehow sneaked its way into this otherwise unrelated commit. So that’s a dead end.
What size of buffer is std::ifstream using? Does increasing that make any difference at all? If the goal is to reduce system call overhead it would seem like an easy way to do that is to reduce the number of system calls by making each one read more data.
an i/o system call that happens magically as a page fault is still a system call.
Is it still a system call though? A “context switch”, sure. But I wouldn’t think of this as a system call myself.
Is it still a system call though? A “context switch”, sure. But I wouldn’t think of this as a system call myself.
It is not an explicit system call, but I would expect a fault to be as expensive as a system call: your CPU state has to be preserved, we have to go to another privilege level which means dumping out all the speculation stuff and doing all the other expensive mitigation for the sieve-like nature of modern CPUs, and you’re then in the kernel futzing with data structures and potentially arranging disk I/O and putting the thread to sleep until that comes back.
The only time mapping memory is cheaper is when there are enough spare resources (address space in the user process, physical RAM for the page cache in the kernel) to have already mapped the things you need – and the kernel has to have been smart enough to read it in the background on its own and arrange the mappings in advance. But then who gets billed for that background work is always an interesting (and frequently unanswered) question in models like prefetched mappings or even the newer completion ring style APIs.
This kinda seems like doing things the hard way. It will work, eventually. But if you avoid the use of all the tools that are designed to make your life easier you’re going to be spending a lot of time trying things with no guarantee of progress. There was no attempt to use valgrind. There was a brief mention of ASan but it is dismissed without trying it because it “probably won’t be of use”.
Where did the original binaries come from? I see that they were living in /usr/bin, which means there is a good chance they were installed via the operating system’s package manager. If this is accurate then there’s a decent chance that the debug symbols (that have been stripped from the binaries in the package) are installable as a separate package.
Installing these packages would provide a meaningful backtrace in the debugger when a crash occurs.
If for some reason you cannot get debug symbols this way, how about doing a release build with debug information included using -g? A build created this way should crash just the same as the release build that has had its debug information stripped, but will provide a meaningful backtrace in the debugger when a crash occurs. It won’t be as nice to use in the debugger as a dedicated debug build but when the problem doesn’t reproduce in a debug build, debugging the release build is better than nothing.
I think it’s possible that the author is not aware of the tools or, in the case of the packaged debug symbols, not aware that such an option exists.
I regularly catch myself forgetting about debug tools at my disposal. I often work in embedded environments where for some reason or another, be it a compiler from the 90ies or a toolchain issue (the toolchain often being supplied by a vendor) it is not possible to use all the nice tools available when doing development on a “big machine”. Then, when going to back to development on a mainstream OS, I somehow forget to break out of the old habits.
If everything is still an fd I’m not sure what the difference between actually implementing this approach and acting as if this approach was implemented. Nothing is stopping you from using pread/pwrite on files (obviously) and just never calling lseek. An error from read on a file is not much different than an error from lseek on a pipe or socket, or named pipe, not to mention platform specific things like timerfds and what not.
Also unless you also remove dup altogether you just shift the problem to when you duplicate the post-streamified fd. Even if lseek is gone reads on the two fds will interfere with the current position in the same way.
I could see this working if fds and sds (“stream descriptors”) were different types but I think the existence of fifos means open can’t return just fds (non-streamified descriptors).
You can avoid calling lseek yourself but if you dup a descriptor and hand it off to a library or another process you can’t control whether or not it calls lseek on its descriptor. I guess if it decides to do that you’d still be fine as long as you only used pread/pwrite and never did anything that read the file position.
I’m not entirely clear on the author’s proposal but it sounds like the idea is that if you dupped a “streaming view” of a file then the duplicated copy would have its own independent file position? Or maybe dup on a “streaming view” works the same way that things do now (with a shared file position) but if that bothered you then you could choose to not call dup on the streaming view. Instead you’d create a brand new streaming view from the same underlying file. Then each streaming view would have its own position and you could hand one to other code without worrying about the effects on your own streaming view.
Of course none of this solves the issue of what do to if you have a real stream (not a streaming view of a file) like a pipe. If you dup it then a read from any copy will consume the data and that data won’t be available on other copies of the descriptor. Maybe this is simply defined as the expected behavior and thus as OK?
Named pipes (FIFOs) would complicate things. But this article seems like it proposing an alternative OS design that is not POSIX but is instead “POSIX-like”. In this alternative world we could say that named pipes are not supported. Or that they have to be opened with a special open_named_pipe syscall. Or that the file descriptor returned by calling open on a named pipe is a special stub descriptor. Attempting to call pread/pwrite on the stub will fail. The only way to do anything useful with the stub would to be to create a streaming view from it and then call read/write on that streaming view. This is admittedly kind of ugly but that’s the price for maintaining the ability to open named pipes with open.
There are probably other complications. How do you handle writes to files open for O_APPEND? Does pwrite write to the file at the requested offset or does it ignore that offset and write at the end? If it does write at the requested offset, how can you atomically append some data to the file? You can’t ask for the current size and then write at that offset because the file size might change between the first call and the second.
What do you do about select and poll and friends? Do these take streaming views instead of file descriptors now?
Overall I don’t hate the idea. If we were going to put this in object-oriented terms then the current system would have pread and pwrite methods on the file descriptor interface. But some classes that implement that interface (like pipes, sockets, etc.) don’t support those operations so they just fail at runtime if you try to call those methods. Usually this is a sign that you’ve got your interfaces designed poorly. The most obvious fix for this type of thing would be to split things up into two (or more) interfaces and have each class implement only the methods that make sense for that particular class, and maybe create some adapter classes to help out. That seems to be what’s being proposed here, with the role of the adapter class being played by the “streaming view”. The most significant difference that I can see is that constructing new wrapper objects would normally be considered fairly cheap but constructing the streaming view would require an extra syscall which could be expensive enough that people would want to avoid it.
I wonder if it would be possible to emulate the streaming view in userspace in some place like the C library. That would get the kernel entirely out of the business of maintaining file offsets and leave them up to the process to track. The C library would be allowed to call read and write on objects like pipes and sockets but for real files it would only be allowed to call pread and pwrite. If the user code creates a streaming view of a file and tries to call read on it then the C library would have to translate that request to an equivalent pread call and then update the file position stored in the streaming view. Doing this for any POSIX environment would probably be somewhere between difficult and impossible but maybe one can imagine an OS design where it could be made to work.
My point isn’t that “this isn’t necessary because discipline”, it’s “the amount that this helps doesn’t reduce the discipline required in any significant way.” Everything is still read(Object, … ), pread(Object, …), ioctl(Object, …) etc. Removing lseek doesn’t stop two processes or threads from interfering with each other with read and its implicit seeks on a pipe, socket or streamed file.
I would exercise caution with the Linux version. It will work fine for processes under a debugger or processes that aren’t being ptraced at all. But for things that are being ptraced by something other than a debugger (like strace, or ltrace), it will abruptly kill that process.
I know it’s bad form to nitpick the introduction of an otherwise-great article which is itself an explanation of another great article, but something about the opening categorization of languages bothered me. To reword:
Data races provoke undefined behavior
Data races provoke object tearing, dirty reads, or other bugs common to data races
Data races are not possible
I’m not sure whether this is exhaustive. There should be at least one more family: languages where data races are possible, but do not result in bugs. In E, the only possible data race is when a promise resolves multiple times within a single turn, and E defines this case to cause an exception for whichever resolver loses. In Haskell, with the STM monad, data races become transactions and losing writers are able to retry until they succeed/win. In both languages, data races turn from a bug into a feature, because multiple resolvers/writers can cooperate to incrementally advance a distributed state. (Sure, the state is only distributed between threads, but sharing state between threads is hard!)
I’m not familiar with the E language so I’m basing the following entirely on the description that you’ve provided above. If a promise resolves multiple times and this situation is reliably detected and turns into an exception then it sounds to me like there are no data races in E. If there were data races then I would expect undesirable things to happen. One result could be “silently” lost, or the result could change out from under you once you’ve observed it, etc.
two or more threads in a single process access the same memory location concurrently, and
at least one of the accesses is for writing, and
the threads are not using any exclusive locks to control their accesses to that memory.
If E can reliably detect the situation that you described then I would think that under the covers it must be using a lock or atomic memory operations in the implementation. If that’s correct then there would be no data races.
Are you perhaps thinking of “race condition” instead of “data race”?
E and Haskell are implemented with green threads. Perhaps this makes them unfair examples, since we would want to see them implemented with OS threads before believing my claims. Indeed, I think it is fair to say that data races aren’t possible in E. I gather that STM still works with OS threads, though it uses atomic compare-and-swap operations.
They’re likely using modern x86 CPUs. They do have instructions to accelerate crc32/crc32c. I have no idea why they’re not just using those.
It is also very complicated-looking high level code that smells of premature optimization, potentially doing way more damage than good on a modern compiler. An asm implementation would be like 30 lines.
I wonder what a clean way to do this would be. Maybe some assembly implementations in their own source files, for different CPUs, with some runtime detection, with a C or C++ implementation as fallback.
Why did ktrace decode this to VIDIOC_S_FMT when it actually wasn’t? This would probably have been a much shorter trip if ktrace wasn’t being actively misleading.
If you look at the kdump(1) source, there’s a step where it scans the header files and tries to build a mapping between the ioctl command value and the text name. The challenge is a command value isn’t really a way to uniquely describe the ioctl(2) call since it’s really the combo of device (as fd) and command.
This means it’s best effort to keep the command values globally unique using some macros like _IOWR, _IOW, etc. Sadly we have some collisions to fix. :-)
edit: I think I misinterpreted your question…I still believe there’s probably something going on in kdump(1), which is where the human translation occurs. Have to look.
/*
* Ioctl's have the command encoded in the lower word, and the size of
* any in or out parameters in the upper word. The high 3 bits of the
* upper word are used to encode the in/out status of the parameter.
*/
So the signed/unsigned interpretation can goof up the “inout” directional part, but leave the identifying group and num parts intact. Have to look at kdump(1) to see how this manages to still be recognized, though.
The compiler can (and will) optimize the code according to the const directives.
Not really. Variables without the const annotation that aren’t modified will be optimized the same. Optimizers transform code to single-assignment form anyway.
And for all indirect data (including variables whose address has “escaped”) C++ gives no guarantees about immutability, even when they’re const. const only means that you can’t modify it through this particular binding, not that it can’t be modified.
There’s still value in trying to declare your intentions and using a more functional style, but don’t expect const to actually do anything in C or C++. It’s a broken design (e.g. const *const * is mutable).
Not really. Variables without the const annotation that aren’t modified will be optimized the same. Optimizers transform code to single-assignment form anyway.
There is one case where this is not true: If you have a global that has its address taken and which escapes from the compilation unit (or has sufficiently complex data flow that alias analysis gives up). Declaring the global (including statics) as const tells the compiler that any store to the global, irrespective of the layers of const casts and arbitrary bits of pointer arithmetic is UB. The compiler can then assume no stores will happen, even if it can’t track all of the places a pointer to the global ends up.
You are correct for all other cases. This one is important though because it’s the one that made a big difference in Jason Turner’s talk (linked from the article).
I don’t think it even needs to be a global. For example, take https://gcc.godbolt.org/z/Tds84bd7d. The compiler knows that i is const. When returning from the function it knows that it could not have changed so it can put 42 as an directly as an immediate in the assembly code (mov eax, 42).
But if you change i to be non-const then it can no longer do this. Instead it has to perform a load from memory to get the current value of i before returning (mov eax, DWORD PTR [rsp+12]).
If you’re wondering how i could possibly ever change, consider that takes_const_ptr might be implemented as *(int *)p = 0;. The standard says that this is completely fine if and only if you never actually pass a pointer to a const-qualified object to takes_const_ptr. So in the case where we pass a pointer to a non-const i, the compiler needs to assume that the value may change. If we’re passing a const i and takes_const_ptr tries to do this trick and modify the value then the result is UB so the compiler can assume that a const i never changes.
I’m curious as to why Base::setName is not pure virtual. That would mean that if you forgot to override it you’d get a compile-time error. Instead of the current situation where if you forget to override it (or attempt to override it but make a mistake) you don’t find out until you get an assertion failure at run-time.
Oh, and GCC also supports -Woverloaded-virtual for anybody who would like to have this warning but isn’t using clang.
I can’t go back far enough in our repository to see if there is a reason for not being an abstract base class, and I cannot open the previous version control system from here (visual sourcesafe) to look back even further.
One of my responsibilities at a previous job was running Coverity static analysis on a huge C codebase and following up on issues. It wouldn’t be uncommon to check a new library (not Curl) of 100k lines of code, and find 1000 memory issues. The vast majority would be pretty harmless – 1-byte buffer overflows and such – but then there were always some doozies that could easily lead to RCEs that we’d have to fix. At the point where the vast majority of people working in a language are messing up on a regular basis, it’s the language’s fault, not the people.
For anyone wondering about setting up static analysis for your own codebase, some things to know!
Static analysis, unlike dynamic analysis, is analysis performed on source code, and encompasses numerous individual analysis techniques. Some of them are control-flow based, some of them are looking for the presence of concerning patterns or use of unsafe functions. As an example, most engines doing checks for null-pointer dereferences are basing their analysis on a control flow graph of the program, looking for places where a potential null assignment could flow to a dereference.
Static analysis is a conservative analysis, meaning it may have false positives. The false positive rate can also be impacted by coding style. That said, most static analyzers are configurable, and you can and should (especially early on) filter and prioritize findings which your current coding practices may flag disproportionately.
Many static analysis tools will give you severity ratings for the findings. These are not the same as CVSS scores (Common Vulnerability Scoring System). They are based on a general expectation for a category of weakness a finding falls into (for example, they might say “CWE-476 (null pointer dereference) is often critical, so we’ll call anything that matches this CWE a ‘critical’ finding.’” These ratings say nothing about whether something is critical in your application, and you can have completely inconsequential findings rated critical, while actually critical vulnerabilities sit in the “low” category.
Additionally, understand that these categories, which are often given on a four-level scale in the popular tools, are defined along two axes: expected likelihood of exploitation and expected severity of exploitation. High likelihood + high severity = critical, low likelihood + high severity = high, high likelihood + low severity = medium, low likelihood + low severity = low.
The nature of the false positive rates means you’re much better off having a regular or continuous practice of static analysis during which you can tune configuration and burn down findings, to increase signal and value over time.
If you have too many findings to handle, you may be tempted to sample. If you do, use standard statistical techniques to make sure your sample size is large enough, and your sample is representative of the population. You may also consider whether the sample is representative of a subset of the population’s CWEs which you consider high priority (you may, for example, choose the regularly-updated CWE Top 25 list, or you may choose something like the OWASP top-10 mapped to CWE).
Hope this helps someone who is interested in setting up a software assurance practice using static analysis!
Conversations arguing over the counterfactual of whether using or not using C would have helped are less interesting than acknowledging, whatever language you’re using, there are software assurance techniques you start doing today to increase confidence in your code!
The thing you do to increase confidence doesn’t have to be changing languages. Changing languages can help (and obviously for anyone who’s read my other stuff, I am a big fan of Rust), but it’s also often a big step (I recommend doing it incrementally, or in new components rather than rewrites). So do other stuff! Do static analysis! Strengthen your linting! Do dynamic analysis! More testing! Formal methods for the things that need it! Really, just start.
Excellent point. Coverity is a really, really good tool; I do wish there were an open-source equivalent so more people could learn about static analysis.
test -f FILE && source $_ || echo "FILE does not exist" >&2
This doesn’t work if the filename has a space or an asterisk in it. You need to surround $_ with double-quotes. https://www.shellcheck.net/ is your friend.
assuming it was added based on @Diti ‘s comment above, it would work for the intended shell, zsh since zsh doesn’t expand unquoted variables the same. I agree the author should probably update this.
Indeed, echo can fail. Redirecting stdout to /dev/full is probably the easiest way to make this happen but a named pipe can be used if more control is required. The sentence from the article “The echo command always exists with 0” is untrue (in addition to containing a typo).
Or &&true at the end, if it’s okay for this command to fail. EDIT: see replies
It’s as much of a kludge as any other, and I’m not sure how to save the return value of a command here, but bash -ec 'false && true; echo $?' will return 0 and not exit from failure. EDIT: it echoes 1 (saving the return value), see replies for why.
I did mean || true, but in the process of questioning what was going on I learned that && true appears to also prevent exit from -eand save the return value!
Echoes 3. I used a function and return to prove it isn’t simply a generic 1 from failure (as false would provide). Adding -x will also show you more of what’s going on.
I personally use the following formatting, which flips the logic, uses a builtin, and printd to stderr.
[ "${USER}" == "root" ] || {printf "%s\n" "User must be 'root'" 1>&2; exit 1; }
When I start doing a larger amount of checks, I wrap the command group within a function, which turns into the following, and can optionally set the exit code.
die() { printf "%s\n" "${1}" 1>&2; exit ${2:-1}; }
...
[ "${USER}" == "root" ] || die "User must be 'root'"
I’ve been running a Mozilla DXR instance for our internal code. Does anyone have experience with both? What are the advantages of sourcegraph over DXR?
I’ve also been running a Mozilla DXR instance. I’ve been very happy with it. Disclaimer: I have been a contributor to DXR in the past.
I only have minimal experience with Sourcegraph. Sourcegraph does fairly well in my opinion. The only annoying thing that I notice missing is “Find declarations”. You can search for references and it looks like any declarations are in that list but there is no easy way to find the declaration(s) separately.
The main problem with DXR is that it has no future. Development has been abandoned. Any development effort had migrated to SearchFox. DXR was explicitly designed to be able to index arbitrary code but it appears that SearchFox may be designed only to index Firefox. I’ve never tried to use it so I don’t know how easy it would be to get your own custom code indexed by a SearchFox instance. With the recent layoffs at Mozilla I doubt even SearchFox is going to be getting much work done on it. DXR only works with ElasticSearch 1.7.x and not newer versions which is becoming increasingly difficult to deal with.
Sourcegraph has support for a lot more languages than DXR so if you’re using something other than Python, Javascript, Rust or C++ it will probably provide a better experience.
If you want to see what using Sourcegraph is like, they have a version at https://sourcegraph.com/search that indexes a bunch of public repos from GitHub. They have the DXR GitHub repo indexed so we can search within that.
Thanks for the explanation! I had a closer look and it seems pretty good. If I ever have to setup a code searching tool again it will probably be sourcegraph. Our current setup still runs on Ubuntu 16.04 which will lose support in 2021. I remember trying to get DXR running on Ubuntu 20.04 but it was too much of a pain due to dependencies on old software (like the old Elasticsearch). The only potential issue with sourcegraph is that multi-branch indexing is still experimental and we will need that. At the moment I think Mozilla’s future is too uncertain to invest much time in searchfox.
You can restrict the commits searched to initial commits with array_length(parent). Playing with that column, apparently some commits manage to have dozens of parents. I don’t even know how you manage that.
select lower(trim(message)) as message, count(*)
from bigquery-public-data.github_repos.commits
where (array_length(parent)=0)
group by message
order by count(*) desc
limit 100;
Hehe yes it was to filter out the empty commits. You’re technically right, but I was more interested in the actual text in the initial commit messages, although like you mention it is worthy to note that empty messages are up there. However, it’s less clear that the empty commit messages are actually initial commit messages since they could come from detached head states with no parents.
Haha just reading this - and it’s funny you mention this because I realized the same way of identifying initial commits in a discussion on the Reddit thread yesterday. I think this way is certainly more accurate than the method I used (just looking through the top counts and picking ones that looked like initial commit messages). I will most likely update my post to reflect this method.
Do you think should file paths on Unix systems be UTF-8 instead of simply being encoding-agnostic byte sequences terminated by 0x00 and delimited by 0x2F? I can see it both ways personally. As it stands, file paths are not text, but they’re nearly always treated as text. All text definitely should be UTF-8, but are file paths text? Should they be text?
Paths consist of segments of file names. File names should be names. Names should be text. Text represented as a sequence of bytes must have a specified encoding, otherwise it’s not text. Now the only question left is: which encoding should we use? Let’s just go with UTF-8 for compatibility with other software.
I would actually put further restrictions on that:
file names should consist of printable characters — what good is a name if the characters it’s made of cannot be displayed?
file names shouldn’t be allowed to span multiple lines — multiline file names will only cause confusion and will often be harder to parse (not just for humans, but also for CLI programs)
As it is now in Unix, file names aren’t for humans. And neither are they for scripts. They’re for… file systems.
I agree with you about those restrictions. In some sense, Windows has succeeded in this area, where Unix has failed. In Unix:
File names can begin with a hyphen, which creates ambiguity over whether it is a command-line flag or an actual file (prompting the convention of -- separating flags from file arguments).
File names can contain newlines, which creates almost unsolvable problems with most Unix tools.
File names cannot contain forward slashes, and thus cannot be confused with command-line flags (which begin with a slash).
File names cannot contain line feeds or carriage returns. All characters in the range 0-31 are forbidden.
File names cannot contain double quotation marks, which means you can very easily parse quoted file names.
Of course, both allow spaces in file names, which creates problems on both systems. If only shell/DOS used commas or something to separate command-line arguments instead of spaces…
There is a long essay by David A. Wheeler about problems that are caused by weird filenames and what we might do to fix them. Including suggestions on possible restrictions that operating systems might impose on valid filenames. Prohibiting control characters (including newline) is one of the items on the list. Scroll down to the very bottom to see it.
Ideally you want to bring reasonable naming capabilities to folks from the world of non-latin character sets. That’s a really good driver to go beyond existing C-Strings and other “Os String” encodings.
But when you say “UTF-8, but printable”, it’s not UTF-8 anymore. Also, what’s a “line”? 80 characters? 80 bytes? Everything that doesn’t contain a newline? Mh.
Allowing UTF-8 will bring some issues with Right-To-Left override characters and files named “txt.lol.exe” on certain operating systems.
Anything that doesn’t contain a newline. The point is that filenames with newlines in them break shell tools, GUI tools don’t allow you to create filenames with newlines in them anyway, and very few tools other than GNU ls have a reasonable way to present them.
Lots of stuff doesn’t allow you to include the ASCII control plane. Windows already bans the control plane from file names. DNS host names aren’t allowed to contain control characters (bare “numbers and letters and hyphen” names certainly don’t, and since domain registrars use a whitelist for extended characters, I doubt you could register a punycode domain with control characters either). The URL standard requires control plane characters to be percent encoded.
I’ve written more than my fair share of IP stacks, usually for high speed packet capture.
On one of these, every now and again, the stats would show that we were capturing at a line rate of terabits per second (or more), on devices with 100Mb Ethernet.
I tore my hair out trying to reproduce the problem. Turned out every now and again a corrupted IP packet would come through with a header length field that was impossibly small. We’d drop the packet as corrupt and update the “invalid packets” counter, but we also updated the “mbps” counter to show that we still processed that amount of data (i.e. we handled it just fine, we weren’t overloaded, it’s the packet that was wrong), but we used the computed packet length to add to the stats…
My favourite network bug (which, fortunately, I didn’t have to debug) was on our CHERI MIPS prototype. The CPU allowed loads to execute in speculation because loads never have side effects. It turns out that they do if they’re loading from a memory-mapped device FIFO. Sometimes, in speculation, the CPU would load 32 bits of data from the FIFO. The network stack would then detect the checksum mismatch and drop the packet. The sender would then adapt to higher packet loss and slow down. The user just saw very slow network traffic. Once this was understood, the CPU bug was fixed (only loads of cached memory are allowed in speculation). This was made extra fun by branch predictor aliasing, which meant that often the load would happen in speculation on a totally unrelated bit of code (and not the same bit)l
Apparently the Xbox 360 had a special uncached load instruction with a similar bug and the only way to avoid problems was to not have that instruction anywhere in executable memory.
Here’s the story about the Xbox 360 CPU bug: https://randomascii.wordpress.com/2018/01/07/finding-a-cpu-design-bug-in-the-xbox-360/
The “fake threads” discussed here remind me of the Virtual Threads from Java’s Project Loom.
They also sound very much like Green Threads. Rust originally started out with Green Threads and then eventually dropped them and decided to support only Native Threads. Bringing them back now might face a difficult path.
What does “RL:” stand for? The program is called
rusage
so if there was going to be any prefix on these lines I would have expected “RU:” or maybe “rusage:”.I tried to see if there were any hints in the git history. This prefix was added in https://github.com/jart/cosmopolitan/commit/cc1920749eb81f346badaf55fbf79620cb718a55. This commit touches over a thousand files. The description of this commit talks about TLS and makes no mention of rusage. It appears that a fairly large rewrite of rusage somehow sneaked its way into this otherwise unrelated commit. So that’s a dead end.
What size of buffer is
std::ifstream
using? Does increasing that make any difference at all? If the goal is to reduce system call overhead it would seem like an easy way to do that is to reduce the number of system calls by making each one read more data.Is it still a system call though? A “context switch”, sure. But I wouldn’t think of this as a system call myself.
It is not an explicit system call, but I would expect a fault to be as expensive as a system call: your CPU state has to be preserved, we have to go to another privilege level which means dumping out all the speculation stuff and doing all the other expensive mitigation for the sieve-like nature of modern CPUs, and you’re then in the kernel futzing with data structures and potentially arranging disk I/O and putting the thread to sleep until that comes back.
The only time mapping memory is cheaper is when there are enough spare resources (address space in the user process, physical RAM for the page cache in the kernel) to have already mapped the things you need – and the kernel has to have been smart enough to read it in the background on its own and arrange the mappings in advance. But then who gets billed for that background work is always an interesting (and frequently unanswered) question in models like prefetched mappings or even the newer completion ring style APIs.
This kinda seems like doing things the hard way. It will work, eventually. But if you avoid the use of all the tools that are designed to make your life easier you’re going to be spending a lot of time trying things with no guarantee of progress. There was no attempt to use valgrind. There was a brief mention of ASan but it is dismissed without trying it because it “probably won’t be of use”.
Where did the original binaries come from? I see that they were living in /usr/bin, which means there is a good chance they were installed via the operating system’s package manager. If this is accurate then there’s a decent chance that the debug symbols (that have been stripped from the binaries in the package) are installable as a separate package.
Take OpenSUSE for example. The openrct2, openrct2-debuginfo, and openrct2-debugsource packages are available at https://download.opensuse.org/repositories/games/openSUSE_Tumbleweed/x86_64/. These should be installable via the command line. (Is it
zypper
on openSUSE?)For Ubuntu the openrct2 and openrct2-dbgsym packages are available from https://ppa.launchpadcontent.net/openrct2/nightly/ubuntu/pool/main/o/openrct2/. These should be installable via
apt
.Installing these packages would provide a meaningful backtrace in the debugger when a crash occurs.
If for some reason you cannot get debug symbols this way, how about doing a release build with debug information included using
-g
? A build created this way should crash just the same as the release build that has had its debug information stripped, but will provide a meaningful backtrace in the debugger when a crash occurs. It won’t be as nice to use in the debugger as a dedicated debug build but when the problem doesn’t reproduce in a debug build, debugging the release build is better than nothing.I think it’s possible that the author is not aware of the tools or, in the case of the packaged debug symbols, not aware that such an option exists.
I regularly catch myself forgetting about debug tools at my disposal. I often work in embedded environments where for some reason or another, be it a compiler from the 90ies or a toolchain issue (the toolchain often being supplied by a vendor) it is not possible to use all the nice tools available when doing development on a “big machine”. Then, when going to back to development on a mainstream OS, I somehow forget to break out of the old habits.
The advice presented is all helpful IMO.
If everything is still an fd I’m not sure what the difference between actually implementing this approach and acting as if this approach was implemented. Nothing is stopping you from using
pread
/pwrite
on files (obviously) and just never callinglseek
. An error fromread
on a file is not much different than an error fromlseek
on a pipe or socket, or named pipe, not to mention platform specific things like timerfds and what not.Also unless you also remove
dup
altogether you just shift the problem to when you duplicate the post-streamified fd. Even iflseek
is gone reads on the two fds will interfere with the current position in the same way.I could see this working if fds and sds (“stream descriptors”) were different types but I think the existence of fifos means
open
can’t return just fds (non-streamified descriptors).You can avoid calling
lseek
yourself but if youdup
a descriptor and hand it off to a library or another process you can’t control whether or not it callslseek
on its descriptor. I guess if it decides to do that you’d still be fine as long as you only usedpread
/pwrite
and never did anything that read the file position.I’m not entirely clear on the author’s proposal but it sounds like the idea is that if you
dup
ped a “streaming view” of a file then the duplicated copy would have its own independent file position? Or maybedup
on a “streaming view” works the same way that things do now (with a shared file position) but if that bothered you then you could choose to not calldup
on the streaming view. Instead you’d create a brand new streaming view from the same underlying file. Then each streaming view would have its own position and you could hand one to other code without worrying about the effects on your own streaming view.Of course none of this solves the issue of what do to if you have a real stream (not a streaming view of a file) like a pipe. If you
dup
it then a read from any copy will consume the data and that data won’t be available on other copies of the descriptor. Maybe this is simply defined as the expected behavior and thus as OK?Named pipes (FIFOs) would complicate things. But this article seems like it proposing an alternative OS design that is not POSIX but is instead “POSIX-like”. In this alternative world we could say that named pipes are not supported. Or that they have to be opened with a special
open_named_pipe
syscall. Or that the file descriptor returned by callingopen
on a named pipe is a special stub descriptor. Attempting to callpread
/pwrite
on the stub will fail. The only way to do anything useful with the stub would to be to create a streaming view from it and then callread
/write
on that streaming view. This is admittedly kind of ugly but that’s the price for maintaining the ability to open named pipes withopen
.There are probably other complications. How do you handle writes to files open for O_APPEND? Does
pwrite
write to the file at the requested offset or does it ignore that offset and write at the end? If it does write at the requested offset, how can you atomically append some data to the file? You can’t ask for the current size and then write at that offset because the file size might change between the first call and the second.What do you do about
select
andpoll
and friends? Do these take streaming views instead of file descriptors now?Overall I don’t hate the idea. If we were going to put this in object-oriented terms then the current system would have
pread
andpwrite
methods on the file descriptor interface. But some classes that implement that interface (like pipes, sockets, etc.) don’t support those operations so they just fail at runtime if you try to call those methods. Usually this is a sign that you’ve got your interfaces designed poorly. The most obvious fix for this type of thing would be to split things up into two (or more) interfaces and have each class implement only the methods that make sense for that particular class, and maybe create some adapter classes to help out. That seems to be what’s being proposed here, with the role of the adapter class being played by the “streaming view”. The most significant difference that I can see is that constructing new wrapper objects would normally be considered fairly cheap but constructing the streaming view would require an extra syscall which could be expensive enough that people would want to avoid it.I wonder if it would be possible to emulate the streaming view in userspace in some place like the C library. That would get the kernel entirely out of the business of maintaining file offsets and leave them up to the process to track. The C library would be allowed to call
read
andwrite
on objects like pipes and sockets but for real files it would only be allowed to callpread
andpwrite
. If the user code creates a streaming view of a file and tries to callread
on it then the C library would have to translate that request to an equivalentpread
call and then update the file position stored in the streaming view. Doing this for any POSIX environment would probably be somewhere between difficult and impossible but maybe one can imagine an OS design where it could be made to work.My point isn’t that “this isn’t necessary because discipline”, it’s “the amount that this helps doesn’t reduce the discipline required in any significant way.” Everything is still
read(Object, … )
,pread(Object, …)
,ioctl(Object, …)
etc. Removing lseek doesn’t stop two processes or threads from interfering with each other with read and its implicit seeks on a pipe, socket or streamed file.I would exercise caution with the Linux version. It will work fine for processes under a debugger or processes that aren’t being ptraced at all. But for things that are being ptraced by something other than a debugger (like
strace
, orltrace
), it will abruptly kill that process.The part about “Local crashes and global equilibria” reminds me of the story of how AT&T’s entire long distance network went down for most of a day in 1990: https://users.csc.calpoly.edu/~jdalbey/SWE/Papers/att_collapse
I know it’s bad form to nitpick the introduction of an otherwise-great article which is itself an explanation of another great article, but something about the opening categorization of languages bothered me. To reword:
I’m not sure whether this is exhaustive. There should be at least one more family: languages where data races are possible, but do not result in bugs. In E, the only possible data race is when a promise resolves multiple times within a single turn, and E defines this case to cause an exception for whichever resolver loses. In Haskell, with the STM monad, data races become transactions and losing writers are able to retry until they succeed/win. In both languages, data races turn from a bug into a feature, because multiple resolvers/writers can cooperate to incrementally advance a distributed state. (Sure, the state is only distributed between threads, but sharing state between threads is hard!)
I’m not familiar with the E language so I’m basing the following entirely on the description that you’ve provided above. If a promise resolves multiple times and this situation is reliably detected and turns into an exception then it sounds to me like there are no data races in E. If there were data races then I would expect undesirable things to happen. One result could be “silently” lost, or the result could change out from under you once you’ve observed it, etc.
Is this a semantic problem where we’re not using the same definition of “data race”? I’m using the definition from https://docs.oracle.com/cd/E19205-01/820-0619/geojs/index.html
If E can reliably detect the situation that you described then I would think that under the covers it must be using a lock or atomic memory operations in the implementation. If that’s correct then there would be no data races.
Are you perhaps thinking of “race condition” instead of “data race”?
E and Haskell are implemented with green threads. Perhaps this makes them unfair examples, since we would want to see them implemented with OS threads before believing my claims. Indeed, I think it is fair to say that data races aren’t possible in E. I gather that STM still works with OS threads, though it uses atomic compare-and-swap operations.
Thanks for both your perspective and the link.
They’re likely using modern x86 CPUs. They do have instructions to accelerate crc32/crc32c. I have no idea why they’re not just using those.
It is also very complicated-looking high level code that smells of premature optimization, potentially doing way more damage than good on a modern compiler. An asm implementation would be like 30 lines.
It appears that they are using those instructions. If you have at least SSE 4.2 support then they use
_mm_crc32_u64
at https://github.com/facebook/rocksdb/blob/main/util/crc32c.cc#L365. And if you also have additionally havepclmulqdq
support then they compute 3 CRCs in parallel and combine them: https://github.com/facebook/rocksdb/blob/main/util/crc32c.cc#L686.There’s also support for extensions on non-x86 CPUs (ARM and PowerPC) as well as a portable fallback path for when none of the above are available.
Oh, they are indeed. It really looks obtuse.
I wonder what a clean way to do this would be. Maybe some assembly implementations in their own source files, for different CPUs, with some runtime detection, with a C or C++ implementation as fallback.
Why did ktrace decode this to
VIDIOC_S_FMT
when it actually wasn’t? This would probably have been a much shorter trip if ktrace wasn’t being actively misleading.If you look at the
kdump(1)
source, there’s a step where it scans the header files and tries to build a mapping between theioctl
command value and the text name. The challenge is a command value isn’t really a way to uniquely describe theioctl(2)
call since it’s really the combo of device (as fd) and command.This means it’s best effort to keep the command values globally unique using some macros like
_IOWR
,_IOW
, etc. Sadly we have some collisions to fix. :-)edit: I think I misinterpreted your question…I still believe there’s probably something going on in
kdump(1)
, which is where the human translation occurs. Have to look.For example, from
sys/sys/ioccom.h
:The underlying logic:
So the signed/unsigned interpretation can goof up the “inout” directional part, but leave the identifying group and num parts intact. Have to look at
kdump(1)
to see how this manages to still be recognized, though.https://bugs.python.org/issue38980
Thank you!
Not really. Variables without the
const
annotation that aren’t modified will be optimized the same. Optimizers transform code to single-assignment form anyway.And for all indirect data (including variables whose address has “escaped”) C++ gives no guarantees about immutability, even when they’re
const
.const
only means that you can’t modify it through this particular binding, not that it can’t be modified.There’s still value in trying to declare your intentions and using a more functional style, but don’t expect
const
to actually do anything in C or C++. It’s a broken design (e.g.const *const *
is mutable).There is one case where this is not true: If you have a global that has its address taken and which escapes from the compilation unit (or has sufficiently complex data flow that alias analysis gives up). Declaring the global (including
static
s) asconst
tells the compiler that any store to the global, irrespective of the layers ofconst
casts and arbitrary bits of pointer arithmetic is UB. The compiler can then assume no stores will happen, even if it can’t track all of the places a pointer to the global ends up.You are correct for all other cases. This one is important though because it’s the one that made a big difference in Jason Turner’s talk (linked from the article).
I don’t think it even needs to be a global. For example, take https://gcc.godbolt.org/z/Tds84bd7d. The compiler knows that
i
is const. When returning from the function it knows that it could not have changed so it can put42
as an directly as an immediate in the assembly code (mov eax, 42
).But if you change
i
to be non-const then it can no longer do this. Instead it has to perform a load from memory to get the current value ofi
before returning (mov eax, DWORD PTR [rsp+12]
).If you’re wondering how
i
could possibly ever change, consider thattakes_const_ptr
might be implemented as*(int *)p = 0;
. The standard says that this is completely fine if and only if you never actually pass a pointer to a const-qualified object totakes_const_ptr
. So in the case where we pass a pointer to a non-consti
, the compiler needs to assume that the value may change. If we’re passing aconst i
andtakes_const_ptr
tries to do this trick and modify the value then the result is UB so the compiler can assume that aconst i
never changes.Thanks. Interestingly, if you replace the
int
with astruct
containing anint
, gcc no longer performs that optimisation but clang does.I’m curious as to why
Base::setName
is not pure virtual. That would mean that if you forgot to override it you’d get a compile-time error. Instead of the current situation where if you forget to override it (or attempt to override it but make a mistake) you don’t find out until you get an assertion failure at run-time.Oh, and GCC also supports
-Woverloaded-virtual
for anybody who would like to have this warning but isn’t using clang.I can’t go back far enough in our repository to see if there is a reason for not being an abstract base class, and I cannot open the previous version control system from here (visual sourcesafe) to look back even further.
Thanks for the tip on GCC!
One of my responsibilities at a previous job was running Coverity static analysis on a huge C codebase and following up on issues. It wouldn’t be uncommon to check a new library (not Curl) of 100k lines of code, and find 1000 memory issues. The vast majority would be pretty harmless – 1-byte buffer overflows and such – but then there were always some doozies that could easily lead to RCEs that we’d have to fix. At the point where the vast majority of people working in a language are messing up on a regular basis, it’s the language’s fault, not the people.
For anyone wondering about setting up static analysis for your own codebase, some things to know!
Hope this helps someone who is interested in setting up a software assurance practice using static analysis!
Conversations arguing over the counterfactual of whether using or not using C would have helped are less interesting than acknowledging, whatever language you’re using, there are software assurance techniques you start doing today to increase confidence in your code!
The thing you do to increase confidence doesn’t have to be changing languages. Changing languages can help (and obviously for anyone who’s read my other stuff, I am a big fan of Rust), but it’s also often a big step (I recommend doing it incrementally, or in new components rather than rewrites). So do other stuff! Do static analysis! Strengthen your linting! Do dynamic analysis! More testing! Formal methods for the things that need it! Really, just start.
Excellent point. Coverity is a really, really good tool; I do wish there were an open-source equivalent so more people could learn about static analysis.
Be careful with that. Overflowing a buffer even by a single byte can be exploitable.
This doesn’t work if the filename has a space or an asterisk in it. You need to surround
$_
with double-quotes. https://www.shellcheck.net/ is your friend.assuming it was added based on @Diti ‘s comment above, it would work for the intended shell, zsh since zsh doesn’t expand unquoted variables the same. I agree the author should probably update this.
I’ve always felt a bit uneasy about this one. I mean, what if echo fails? :-)
So I usually do
instead… just to be safe.
Indeed, echo can fail. Redirecting stdout to /dev/full is probably the easiest way to make this happen but a named pipe can be used if more control is required. The sentence from the article “The echo command always exists with 0” is untrue (in addition to containing a typo).
Don’t you need
set +e;
beforeecho
, just to be extra safe?I had to look that up.
set +e
disables the-e
option:That’s not enabled by default, though, and I personally don’t use it.
Or
&&true
at the end, if it’s okay for this command to fail. EDIT: see repliesIt’s as much of a kludge as any other, and I’m not sure how to save the return value of a command here, but
bash -ec 'false && true; echo $?'
will return0
and not exit from failure. EDIT: it echoes 1 (saving the return value), see replies for why.You probably mean
|| true
. But yeah, that works!I did mean
|| true
, but in the process of questioning what was going on I learned that&& true
appears to also prevent exit from-e
and save the return value!E.G.,
Echoes
3
. I used a function andreturn
to prove it isn’t simply a generic1
from failure (asfalse
would provide). Adding-x
will also show you more of what’s going on.I personally use the following formatting, which flips the logic, uses a builtin, and printd to stderr.
[ "${USER}" == "root" ] || {printf "%s\n" "User must be 'root'" 1>&2; exit 1; }
When I start doing a larger amount of checks, I wrap the command group within a function, which turns into the following, and can optionally set the exit code.
I also always print to standard out, but I’m pretty sure most shells have echo as a built-in. The form I usually use is
This line isn’t right:
It should be checking
result & 0x80
or something.I’ve been running a Mozilla DXR instance for our internal code. Does anyone have experience with both? What are the advantages of sourcegraph over DXR?
I’ve also been running a Mozilla DXR instance. I’ve been very happy with it. Disclaimer: I have been a contributor to DXR in the past.
I only have minimal experience with Sourcegraph. Sourcegraph does fairly well in my opinion. The only annoying thing that I notice missing is “Find declarations”. You can search for references and it looks like any declarations are in that list but there is no easy way to find the declaration(s) separately.
The main problem with DXR is that it has no future. Development has been abandoned. Any development effort had migrated to SearchFox. DXR was explicitly designed to be able to index arbitrary code but it appears that SearchFox may be designed only to index Firefox. I’ve never tried to use it so I don’t know how easy it would be to get your own custom code indexed by a SearchFox instance. With the recent layoffs at Mozilla I doubt even SearchFox is going to be getting much work done on it. DXR only works with ElasticSearch 1.7.x and not newer versions which is becoming increasingly difficult to deal with.
Sourcegraph has two different ways to index your C++ code: lsif-cpp and lsif-clang, with the latter being the newer, recommended option. The lsif-cpp indexer is based on the DXR clang plugin. Compare https://github.com/sourcegraph/lsif-cpp/blob/master/clang/dxr-index.cpp with https://github.com/mozilla/dxr/blob/master/dxr/plugins/clang/dxr-index.cpp.
Sourcegraph has support for a lot more languages than DXR so if you’re using something other than Python, Javascript, Rust or C++ it will probably provide a better experience.
If you want to see what using Sourcegraph is like, they have a version at https://sourcegraph.com/search that indexes a bunch of public repos from GitHub. They have the DXR GitHub repo indexed so we can search within that.
For example, here are all the places where the string
->get
appears in C++ filesAnd here are all the references to the function
getFileInfo
(look in the bottom frame)Thanks for the explanation! I had a closer look and it seems pretty good. If I ever have to setup a code searching tool again it will probably be sourcegraph. Our current setup still runs on Ubuntu 16.04 which will lose support in 2021. I remember trying to get DXR running on Ubuntu 20.04 but it was too much of a pain due to dependencies on old software (like the old Elasticsearch). The only potential issue with sourcegraph is that multi-branch indexing is still experimental and we will need that. At the moment I think Mozilla’s future is too uncertain to invest much time in searchfox.
You can restrict the commits searched to initial commits with
array_length(parent)
. Playing with that column, apparently some commits manage to have dozens of parents. I don’t even know how you manage that.First few values:
This is called an “octopus merge” if you want to search for more information. You may also be interested in this article about octopus merges in the Linux kernel
FYI I just updated my article to include the
array_length(parent)=0
filter. Thanks for the input!You’re welcome :)
Why did you do this?
AND LENGTH(TRIM(LOWER(message))) > 0
Surely the empty commit message is still a valid commit message?Hehe yes it was to filter out the empty commits. You’re technically right, but I was more interested in the actual text in the initial commit messages, although like you mention it is worthy to note that empty messages are up there. However, it’s less clear that the empty commit messages are actually initial commit messages since they could come from detached head states with no parents.
Also, in case you’re curious, I just wrote a post using a similar method to try and answer What % Of Git Commit Messages Use The Imperative Mood?.
Haha just reading this - and it’s funny you mention this because I realized the same way of identifying initial commits in a discussion on the Reddit thread yesterday. I think this way is certainly more accurate than the method I used (just looking through the top counts and picking ones that looked like initial commit messages). I will most likely update my post to reflect this method.
Not everything is UTF-8, but it should be.
Do you think should file paths on Unix systems be UTF-8 instead of simply being encoding-agnostic byte sequences terminated by 0x00 and delimited by 0x2F? I can see it both ways personally. As it stands, file paths are not text, but they’re nearly always treated as text. All text definitely should be UTF-8, but are file paths text? Should they be text?
Paths consist of segments of file names. File names should be names. Names should be text. Text represented as a sequence of bytes must have a specified encoding, otherwise it’s not text. Now the only question left is: which encoding should we use? Let’s just go with UTF-8 for compatibility with other software.
I would actually put further restrictions on that:
As it is now in Unix, file names aren’t for humans. And neither are they for scripts. They’re for… file systems.
I agree with you about those restrictions. In some sense, Windows has succeeded in this area, where Unix has failed. In Unix:
--
separating flags from file arguments).In Windows, however (source):
Of course, both allow spaces in file names, which creates problems on both systems. If only shell/DOS used commas or something to separate command-line arguments instead of spaces…
Windows also doesn’t allow files named
NUL
,PRN
, orCON
. :DThere is a long essay by David A. Wheeler about problems that are caused by weird filenames and what we might do to fix them. Including suggestions on possible restrictions that operating systems might impose on valid filenames. Prohibiting control characters (including newline) is one of the items on the list. Scroll down to the very bottom to see it.
Ideally you want to bring reasonable naming capabilities to folks from the world of non-latin character sets. That’s a really good driver to go beyond existing C-Strings and other “Os String” encodings.
But when you say “UTF-8, but printable”, it’s not UTF-8 anymore. Also, what’s a “line”? 80 characters? 80 bytes? Everything that doesn’t contain a newline? Mh. Allowing UTF-8 will bring some issues with Right-To-Left override characters and files named “txt.lol.exe” on certain operating systems.
It’s tough, isn’t it? :-)
Anything that doesn’t contain a newline. The point is that filenames with newlines in them break shell tools, GUI tools don’t allow you to create filenames with newlines in them anyway, and very few tools other than GNU
ls
have a reasonable way to present them.Lots of stuff doesn’t allow you to include the ASCII control plane. Windows already bans the control plane from file names. DNS host names aren’t allowed to contain control characters (bare “numbers and letters and hyphen” names certainly don’t, and since domain registrars use a whitelist for extended characters, I doubt you could register a punycode domain with control characters either). The URL standard requires control plane characters to be percent encoded.
\r\n
or\n
? ;) You get my point?If it includes
\r\n
, then it includes\n
.If the goal is to avoid breaking your platform’s own default shell, then the answer should be “whatever that shell uses.”
“Is” and “ought”, however, remain dangerous things to confuse.