49b087f3cd
Previously, we were sending Buffers to the server whenever we had new audio data for it. This meant that for every audio enqueue action, we needed to create a new shared memory anonymous buffer, send that buffer's file descriptor over IPC (+recfd on the other side) and then map the buffer into the audio server's memory to be able to play it. This was fine for sending large chunks of audio data, like when playing existing audio files. However, in the future we want to move to real-time audio in some applications like Piano. This means that the size of buffers that are sent need to be very small, as just the size of a buffer itself is part of the audio latency. If we were to try real-time audio with the existing system, we would run into problems really quickly. Dealing with a continuous stream of new anonymous files like the current audio system is rather expensive, as we need Kernel help in multiple places. Additionally, every enqueue incurs an IPC call, which are not optimized for >1000 calls/second (which would be needed for real-time audio with buffer sizes of ~40 samples). So a fundamental change in how we handle audio sending in userspace is necessary. This commit moves the audio sending system onto a shared single producer circular queue (SSPCQ) (introduced with one of the previous commits). This queue is intended to live in shared memory and be accessed by multiple processes at the same time. It was specifically written to support the audio sending case, so e.g. it only supports a single producer (the audio client). Now, audio sending follows these general steps: - The audio client connects to the audio server. - The audio client creates a SSPCQ in shared memory. - The audio client sends the SSPCQ's file descriptor to the audio server with the set_buffer() IPC call. - The audio server receives the SSPCQ and maps it. - The audio client signals start of playback with start_playback(). - At the same time: - The audio client writes its audio data into the shared-memory queue. - The audio server reads audio data from the shared-memory queue(s). Both sides have additional before-queue/after-queue buffers, depending on the exact application. - Pausing playback is just an IPC call, nothing happens to the buffer except that the server stops reading from it until playback is resumed. - Muting has nothing to do with whether audio data is read or not. - When the connection closes, the queues are unmapped on both sides. This should already improve audio playback performance in a bunch of places. Implementation & commit notes: - Audio loaders don't create LegacyBuffers anymore. LegacyBuffer is kept for WavLoader, see previous commit message. - Most intra-process audio data passing is done with FixedArray<Sample> or Vector<Sample>. - Improvements to most audio-enqueuing applications. (If necessary I can try to extract some of the aplay improvements.) - New APIs on LibAudio/ClientConnection which allows non-realtime applications to enqueue audio in big chunks like before. - Removal of status APIs from the audio server connection for information that can be directly obtained from the shared queue. - Split the pause playback API into two APIs with more intuitive names. I know this is a large commit, and you can kinda tell from the commit message. It's basically impossible to break this up without hacks, so please forgive me. These are some of the best changes to the audio subsystem and I hope that that makes up for this :yaktangle: commit. :yakring: |
||
---|---|---|
.. | ||
Fuzzers | ||
Tools | ||
.gitignore | ||
BuildFuzzers.sh | ||
CMakeLists.txt | ||
get_linked_lagom_libraries.cmake | ||
ReadMe.md | ||
TestApp.cpp | ||
TestJson.cpp |
Lagom
The Serenity C++ library, for other Operating Systems.
About
If you want to bring the comfortable Serenity classes with you to another system, look no further. This is basically a "port" of the AK
and LibCore
libraries to generic *nix systems.
Lagom is a Swedish word that means "just the right amount." (Wikipedia)
Fuzzing
Lagom can be used to fuzz parts of SerenityOS's code base. Fuzzers can be run locally, and they also run continuously on OSS-Fuzz.
Fuzzing locally
Lagom can be used to fuzz parts of SerenityOS's code base. This requires building with clang
, so it's convenient to use a different build directory for that. Fuzzers work best with Address Sanitizer enabled. The fuzzer build requires code generators to be pre-built without fuzzing in a two stage build. To build with LLVM's libFuzzer, invoke
the BuildFuzzers.sh
script with no arguments. The script does the equivalent of the CMake commands below:
# From the Meta/Lagom directory:
# Stage 1: Build and install code generators and other tools
cmake -GNinja -B Build/tools \
-DBUILD_LAGOM=OFF \
-DCMAKE_INSTALL_PREFIX=Build/tool-install
ninja -C Build/tools install
# Stage 2: Build fuzzers, making sure the build can find the tools we just built
cmake -GNinja -B Build/lagom-fuzzers \
-DBUILD_LAGOM=ON \
-DENABLE_FUZZERS_LIBFUZZER=ON \
-DENABLE_ADDRESS_SANITIZER=ON \
-DENABLE_UNDEFINED_SANITIZER=ON \
-DCMAKE_PREFIX_PATH=Build/tool-install \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_COMPILER=clang
cd Build/lagom-fuzzers
ninja
# Or as a handy rebuild-rerun line:
ninja FuzzJs && ./Fuzzers/FuzzJs
(Note that we require clang >= 13, see the pick_clang() function in the script for the paths that are searched)
Any fuzzing results (particularly slow inputs, crashes, etc.) will be dropped in the current directory.
clang emits different warnings than gcc, so you may have to remove -Werror
in CMakeLists.txt and Meta/Lagom/CMakeLists.txt.
Fuzzers work better if you give them a fuzz corpus, e.g. ./Fuzzers/FuzzBMPLoader ../Base/res/html/misc/bmpsuite_files/rgba32-61754.bmp
Pay attention that LLVM also likes creating new files, don't blindly commit them (yet)!
To run several fuzz jobs in parallel, pass -jobs=24 -workers=24
.
To get less log output, pass -close_fd_mask=3
-- but that but hides assertion messages. Just 1
only closes stdout.
It's good to move overzealous log output behind FOO_DEBUG
macros.
Keeping track of interesting testcases
There are many quirky files that exercise a lot of interesting edge cases. We should probably keep track of them, somewhere.
We have a bmp suite and a jpg suite and several others. They are GPL'ed, and therefore not quite as compatible with the rest of Serenity. That's probably not a problem, but keeping "our" testcases separate from those GPL'ed suits sounds like a good idea.
We could keep those testcases somewhere else in the repository, like a fuzz
directory.
But fuzzing tends to generate more and more and more files, and they will blow up in size.
Especially if we keep all interesting testcases, which is exactly what I intend to do.
So we should keep the actual testcases out of the main serenity repo, that's why we created https://github.com/SerenityOS/serenity-fuzz-corpora
Feel free to upload lots and lots files there, or use them for great good!
Fuzzing on OSS-Fuzz
https://oss-fuzz.com/ automatically runs all fuzzers in the Fuzzers/ subdirectory whose name starts with "Fuzz" and which are added to the build in Fuzzers/CMakeLists.txt
if ENABLE_FUZZERS_OSSFUZZ
is set. Looking for "serenity" on oss-fuzz.com finds interesting links, in particular:
Here's Serenity's OSS-Fuzz Config.
To run the oss-fuzz build locally:
git clone https://github.com/google/oss-fuzz/
cd oss-fuzz
python3 infra/helper.py build_image serenity
python3 infra/helper.py build_fuzzers serenity
These commands will put the fuzzers in build/out/serenity
in the oss-fuzz repo. You can run the binaries in there individually, or simply type:
python3 infra/helper.py run_fuzzer serenity FUZZER_NAME
To build the fuzzers using the oss-fuzz build process, but against a local serenity checkout:
python3 infra/helper.py build_fuzzers serenity $HOME/src/serenity/
To run a shell in oss-fuzz's serenity docker image:
docker run -it gcr.io/oss-fuzz/serenity bash
Analyzing a crash
LLVM fuzzers have a weird interface. In particular, to see the help, you need to call it with -help=1
, and it will ignore --help
and -help
.
To reproduce a crash, run it like this: MyFuzzer crash-27480a219572aa5a11b285968a3632a4cf25388e
To reproduce a crash in gdb, you want to disable various signal handlers, so that gdb sees the actual location of the crash:
$ gdb ./Fuzzers/FuzzBMP
<... SNIP some output ...>
(gdb) run -handle_abrt=0 -handle_segv=0 crash-27480a219572aa5a11b285968a3632a4cf25388e
<... SNIP some output ...>
FuzzBMP: ../../Userland/Libraries/LibGfx/Bitmap.cpp:84: Gfx::Bitmap::Bitmap(Gfx::BitmapFormat, const Gfx::IntSize &, Gfx::Bitmap::Purgeable): Assertion `m_data && m_data != (void*)-1' failed.
Thread 1 "FuzzBMP" received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: File or directory not found.
(gdb)
UBSan doesn't always give useful information. use something like export UBSAN_OPTIONS=print_stacktrace=1
to always print stacktraces.
You may run into annoying issues with the stacktrace:
==123456==WARNING: invalid path to external symbolizer!
==123456==WARNING: Failed to use and restart external symbolizer!
That means it couldn't find the executable llvm-symbolizer
, which could be in your OS's package llvm
.
llvm-symbolizer-11
will not be recognized.
Using Lagom in an External Project
It is possible to use Lagom for your own projects outside of Serenity too!
An example of this in use can be found on Linus' LibJS test262 runner.
To implement this yourself:
- Download a copy of linusg/libjs-test262/cmake/FetchLagom.cmake and place it wherever you wish
- In your root
CMakeLists.txt
, add the following commands:include(FetchContent) include(cmake/FetchLagom.cmake) # If you've placed the file downloaded above differently, be sure to reflect that in this command :^)
- In addition, you will need to also add some compile options that Serenity uses to ensure no warnings or errors:
add_compile_options(-Wno-literal-suffix) # AK::StringView defines operator"" sv, which GCC complains does not have an underscore. add_compile_options(-fno-gnu-keywords) # JS::Value has a method named typeof, which also happens to be a GNU keyword.
Now, you can link against Lagom libraries.
Things to keep in mind:
- You should prefer to use a library's
Lagom::
alias when linking- Example:
Lagom::Core
vsLibCore
- Example:
- If you still need to use the standard library, you may have to compile with the
AK_DONT_REPLACE_STD
macro.- Serenity defines its own
move
andforward
functions inside ofAK/StdLibExtras.h
that will clash with the standard library's definitions. This macro will make Serenity use the standard library'smove
andforward
instead.
- Serenity defines its own