Programming FAQ

From BCI2000 Wiki
Jump to: navigation, search

Contributing

Q: I wrote a piece of software that I would like to add to BCI2000 as a contribution. How do I proceed?

A: See Contributions:Contents#How to contribute for a suggestion.

Debugging

Q: How do I run a BCI2000 module from a debugger?

A:

  1. Compile the module in debug mode:
  • If using Visual Studio, switch to debug mode, right-click the module's project, and choose "Rebuild" from the context menu.
  • If building from the command line, re-run the appropriate shell script from the BCI2000/build folder, with -DCMAKE_BUILD_TYPE=Debug added as a command-line option, e.g.
cd BCI2000/build
./"Create Unix Makefiles.sh" -DCMAKE_BUILD_TYPE=Debug
make MySignalProcessing
  1. Add the # comment indicator in front of the module's startup command in the batch file you are using to start up BCI2000. If you are using BCI2000Launcher, adapt one of the batch files in BCI2000/batch to start up your desired configuration, and use that batch file during debugging.
  2. Execute the batch file. This will start all required modules except the one you are debugging.
  3. Run the module in the debugging environment.
  • Make sure to run the module with its working directory set to the BCI2000/prog folder, and all command line options (remaining text after the module name, on the same line in the batch file) are present when the module is run.
  • In Visual Studio, these settings are accessible under the project's properties, under "General Properties->Debugging".
  • For other IDEs, these settings may be global debugging settings, per-project settings, or optional per-project settings overriding a global default. Look up your IDE's documentation to learn more.
  • When using a debugger from the command line, change directory to BCI2000/prog, and run the debugger from there.

Q: How do I debug a crash in a BCI2000 module?

A: If the crash occurs in an unmodified module coming with BCI2000, we would very much appreciate a bug report containing information about how to trigger it, preferably using parameter and batch files as coming with BCI2000, or with appropriate files attached to the bug report. Bugs may preferably be reported through the trac ticket system (use the "login" link on the top right, and log in with your BCI2000 user account, then create a bug ticket), but posting on the "known issues" forum is also fine. Actual crashes (i.e., issues that lead to termination of a module) will be fixed as soon as possible, which will usually take from a few days up to a couple of weeks.

If the crash occurs in a module that has been modified by adding your own code, or by making changes to existing code, you should first try to reproduce it with an unmodified BCI2000 module that comes closest to your modified module. If the crash only happens in conjunction with your modifications, the following information may be helpful to you:

Recent versions of BCI2000 (>=3.06) provide a feature intended to simplify debugging of program crashes. On crashing, a module that has been compiled in release mode will try its best to provide a list of functions that were involved in the crash ("call stack"). Due to inherent difficulties in obtaining a call stack from optimized code, the call stack may be incomplete, or it may contain a few bogus function names unrelated to the crash. Still, the list of function names, in conjunction with the situation that triggered the crash, will give you a hint towards what went wrong. Chances are that you will find a bug in your code by analyzing the functions listed in the call stack, beginning at the topmost function that was written or modified by yourself.

If there is no call stack available, or you cannot figure out the problem based on the information in the call stack, you will need to build the module in debug mode. This will provide you with an appropriate call stack in the error message, and will allow you to attach the module to a debugger.

  • Make sure the crashing module is compiled in debug mode, as described in the previous answer.
  • On Windows, you need not modify the batch file. Just run BCI2000 as you would normally.
  • On other systems, modify the batch file as described in the previous answer, and run it. Then, from a terminal window, change directory to BCI2000/prog, and run the module from the terminal, keeping it in the foreground, e.g. (note there is no ampersand):
    ./MySignalProcessing --local
  • Try to trigger the crash by reproducing the situation in which it occurred originally. If you cannot trigger the crash in debug mode, read the answer to the next question.
  • If you succeed, and the module crashes, it will halt execution of the crashing thread, and allow you to attach a debugger.
    • On Windows, this is done by displaying a message box.
    • On other systems, the module will wait for input on the terminal window where it has been started from.
  • You may attach a debugger to the module's process. E.g., when using VisualStudio, choose "Attach to Process..." from the Debug menu, and select your module from the list of processes.
  • Once attached, tell the debugger to halt process execution. It will stop within a library function that is waiting for user input. Walking up the call stack, ignore all library functions, until you meet either
    • a BCIException constructor, or
    • a Windows Exception handler from BCI2000's ExceptionCatcher class, or
    • a POSIX signal handler from BCI2000's ExceptionCatcher class.
  • Walking up one more function in the call stack will show you the function in which the crash occurred. If it is a BCI2000 framework function that threw a bciexception, the previous code lines will probably tell you the reason. Otherwise, examination of the crashing line, and the values of the variables used on that line, will provide you with the immediate reason for the crash.
  • Walking up the call stack until you hit a function written by yourself will provide you with the origin of the crash in your own code.
  • If your own code does not appear in the call stack, you may have hit a bug in the BCI2000 framework, or your own code may have gone astray in ways affecting the BCI2000 framework, e.g. by inadvertently overwriting some memory in use by the framework. If you think that you have found a bug in BCI2000, you may report it here (use the "login" link on the top right, and log in with your BCI2000 user account). Your bug report will be most helpful if you add a description how to trigger the bug in one of the modules coming with BCI2000, if necessary with a minimum amount of code added for demonstration purposes. Actually, determining the minimum amount of changes to an existing module in order to trigger a bug is part of the effort you will need to spend on the question whether it was caused by interference with your own code, or not.
  • Once you are done analyzing the module's current state, there is no more information to be gained about the cause of the crash. You may simply abort execution from the debugger.
  • Alternatively, you may detach the debugger from the module, and press "Enter" in the module's message box, or terminal window. This will cause module termination in the same way as it happens for release builds.
  • Either way, make sure to terminate remaining parts of BCI2000 through the Operator module's "Quit" button, as you would do normally. Otherwise, you may see bogus errors caused by incomplete initialization next time you run BCI2000.

Q: My code runs fine when compiled in debug mode, but crashes in release mode. How is this possible? How may I fix it?

A: You should be aware of the fact that code compiled in debug mode is rather different from code compiled in release mode:

    • it represents your source code in a straightforward way, without any optimizations,
    • it contains additional run-time checks which are omitted in release code,
    • it contains additional information required by the debugger to associate variable names with memory locations, and machine code locations with lines in source code, and function names.

For these reasons, code compiled in debug mode will be both larger and slower than code compiled in release mode. The absence of optimizations, and presence of run-time checks may not only result in a linear increase in execution time but scaling behavior may be affected as well, e.g., code that runs in linear time in a release build may require quadratic time in a debug build, which may make it practically impossible to test a debug build under production conditions.

Finally, the absence of optimizations has an implication that may be considered most harmful in practice: Namely, there exist bugs which will apparently not exist in debug builds. Such bugs are mostly due to subtleties in the rules of the language you are using -- especially C++ is full of such subtleties, which, as a general rule, one is not aware of, until one's ignorance results in a bug impossible to catch in a debugging session. Compiler optimizations, however, make use of such rules in order to rearrange code in complex ways, or to replace entire portions of code with faster equivalents, even across function boundaries. As a result, the behavior of a debug build will match a naive programmer's idea of what his code should behave like, whereas the behavior of a release build will reflect his violations of the rules.

To find such a bug is obviously difficult. The following may be helpful:

  • When using MSVC, run a "Release with Debug Info" under the debugger with all exceptions enabled. This may provide you with a name of the function where the crash apparently occurs. Be aware that optimization may choose to replace a function call with the body of the called function itself, so any function called from the crashing function may contain the bug, as well as any of its callees.
  • Similarly, on Unix-based systems, the debugger should be able to show function names in release builds if the executable is not stripped by the linker.
  • In candidate functions, check each individual line whether it contains function calls or operators within function arguments. Arguments may be evaluated in any order prior to calling a function, and certain orders of evaluation may be faster than others due to utilization of processor resources, so make sure not to depend on that order. E.g., the behavior of
    f(a,++a);
    will be unspecified in C++ -- the function may be called with two different values, or with the same value in both arguments, depending on the order of evaluation. If unsure, rewrite your code such that only a single function call exists on each line of code, and count each operator as a function call.
  • In multithreaded code, make sure that access to data shared between threads is always synchronized with a mutex. This way, you will be sure that the optimizer does not reorder code in a way that interferes with the logic of how your threads interact. Without synchronizers, the optimizer may choose any order of execution that is compatible with single-threaded execution of your code! If you are experienced in a language such as Java and C#, it is important to understand that the volatile keyword does have the same meaning in C/C++. Unlike Java and C#, volatile in C++ does not imply synchronized access, and presence of volatile objects does not prevent an optimizer from rearranging the order of execution.
  • Enable compiler warnings, and try to understand why each of those is issued. (Don't try this when using MSVC, though.)
  • When using MSVC, use a different compiler with warnings enabled, and try to understand those (gcc/MinGW).
  • Try out various optimization settings in the release build in order to identify the type of optimization that triggers the bug.

Q: I keep getting "Unexpected system state transition 11->1" error messages from the operator module.

A: Basically, this error means that the operator module receives messages from core modules that are incompatible with its present state, e.g. a module sends a parameter list but the operator module is not in the publishing phase, and thus cannot respond appropriately. Often, this error is caused when one runs a module from the debugger, aborts its execution, and then restarts it. Then, the BCI2000 system as a whole will be in an undefined state. Rather than just restarting a single module, you need to quit BCI2000 from the operator module, and then restart all modules. Similarly, a module may be crashed, and still be running even after you quit BCI2000. Check for this in the Task Manager's process view, and kill the runaway module from there before restarting BCI2000. Also, if the runaway module is part of the BCI2000 core distribution, we will be glad if you drop us a note on the BBS.

Source Code Access

Q: When trying to get access to the BCI2000 source code, my user account and password don't work.

A:

  • Make sure you are using the Wiki/SVN account information that was sent to you per e-mail, not the BBS account, which is different.
  • Make sure your SVN client knows how to do Digest Authentification. Some precompiled SVN versions for Mac OS X have been reported not to work (the Fink distribution should).
  • Avoid interference from firewalls by using SSL rather than plain HTTP communication.

App Connector

Q: I want to replace the BCI2000 application module with an external application written in my favorite programming environment. This should be possible using the App Connector protocol. However, when I replace the application module with an empty dummy one, my external application does not get any data.

A: If you are going to use the App Connector, then your application module needs to contain both ConnectorInput and ConnectorOutput filter as a minimum configuration.

Getting Started with a new Module

Q: How should I begin working on a new BCI2000 module?

A: BCI2000 provides a mechanism to create new BCI2000 modules from templates. To create a new BCI2000 module, run BCI2000/build/NewBCI2000Module; for a new filter, run BCI2000/build/NewBCI2000Filter. Both executables will only be present when you have built BCI2000 first as described here.

Writing Filter Components

Q: I would like to write my own signal processing filter. Where is a good place to start?

A: Writing your own signal processing filter itself is described at: Programming Tutorial:Implementing a Signal Processing Filter.

General information on signal processing modules is found at Technical Reference:Core Modules#Signal Processing Module and User Reference:Filters.


Q: I wrote a signal processing filter, data acquisition component (ADC filter), or data format component (FileWriter). I added its cpp file to the appropriate module, everything compiles and links fine, but I cannot use it -- everything appears as if the new component were nonexistent. What is wrong?

A: Make sure that your component is instantiated.

  • For source and application components, make sure that there is a line RegisterFilter( YourFilterClassName, Position ); somewhere in its cpp file (preferably at the top, following #include statements).
  • For signal processing filters, make sure that your filter is listed in the signal module's PipeDefinition.cpp as a statement Filter( YourFilterClassname, Position );