Manual dynamic analysis
When is manual dynamic analysis relevant?
Though beLow allows you to run dynamic analysis automatically, this is not always possible, depending on your use case.
Automated dynamic analysis will not be possible if:
You are running your application in an environment unable to print data to a file (usage of
fprintfnot possible)Running your application is not scriptable (e.g., requires clicking in some Windows software, requires human interaction, etc)
In this context, there are two possibilities:
Running static analysis only: beLow will still be able to find some optimizations, but their impact will certainly be under or over-evaluated. An optimization in an initialization function and an optimization in a deep for-loop would have the same weight, while in runtime reality, the first one would be executed once and the second one million times.
Running manual dynamic analysis: in this case, we provide you an instrumented code that you manually have to build, run, and retrieve data from.
Manual dynamic analysis is not suitable in fully automated environments like CI/CD, but it is recommended when you want accurate insights about what beLow can improve in your code.
How to run a manual dynamic analysis?
When setting up a project without automated dynamic analysis, before running analysis, you get a card prompting either to run static analysis or to use manual dynamic analysis.
Before analysis
At this point, you have 2 choices:
Run a static analysis by clicking Run analysis, or
Perform a manual dynamic analysis
The steps to run a manual dynamic analysis are:
Download instrumented code
Click the corresponding button in the UI to download instrumented-code.zip. Unzip it. The archive contains:
main.c: the instrumented code. Ideally do not modify this file.below_vendor.i: beLow vendor generated code (not supposed to be modified).below_instr.i: beLow customizable code which you should modify as needed.
Merge the unzipped code into your original project (or a copy). The instrumented code adds counters to measure execution frequency of code blocks and formats data for export.
Merge and optionally adapt instrumented code
Merge the instrumented files into your project. You may:
Apply the instrumented files directly to the original project (you can revert later with git), or
Work in a copy of your project.
Default behavior implemented by the instrumented code:
Allocates a global array of counters
Increments counters (one index per instrumented block)
Prints counters as text into a file named
wedolow.profwhen the program exits
Modify below_instr.i if your environment requires different behavior (for example, sending profile output over UART on a microcontroller).
In the next section we walk through a full example.
Example project
The example project is a single-file C program that sums two random integer vectors every 1 second in an endless loop.
main.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define N 5
void vect_add(int *a, int *b, int *c, int n) {
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
}
int main() {
int *a = malloc(N * sizeof(int));
int *b = malloc(N * sizeof(int));
int *c = malloc(N * sizeof(int));
if (!a || !b || !c) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
srand(time(NULL));
while (1) {
for (int i = 0; i < N; i++) {
a[i] = rand() % 100;
b[i] = rand() % 100;
}
vect_add(a, b, c, N);
printf("a: ");
for (int i = 0; i < N; i++) printf("%d ", a[i]);
printf("\nb: ");
for (int i = 0; i < N; i++) printf("%d ", b[i]);
printf("\nc: ");
for (int i = 0; i < N; i++) printf("%d ", c[i]);
printf("\n\n");
sleep(1);
}
free(a);
free(b);
free(c);
return 0;
}Build command used in the example:
gcc -O2 -o exec main.cppDownload instrumented code
After unzipping, merge the instrumented files into the project as described above.
The instrumented main.c contains calls to BELOW_COUNT(id) and an include of below_instr.i, for example:
main.c (instrumented)
#include "below_instr.i"
void BELOW_COUNT(int id);
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define N 5
void vect_add(int *a, int *b, int *c, int n) {
{BELOW_COUNT(1);}
for (int i = 0; i < n; i++) {
{BELOW_COUNT(2);}
c[i] = a[i] + b[i];
}
{BELOW_COUNT(3);}
}
int main() {
{BELOW_COUNT(0);}
int *a = malloc(N * sizeof(int));
int *b = malloc(N * sizeof(int));
int *c = malloc(N * sizeof(int));
if (!a || !b || !c) {
{BELOW_COUNT(4);}
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
{BELOW_COUNT(5);}
srand(time(NULL));
while (1) {
{BELOW_COUNT(6);}
for (int i = 0; i < N; i++) {
{BELOW_COUNT(8);}
a[i] = rand() % 100;
b[i] = rand() % 100;
}
{BELOW_COUNT(9);}
vect_add(a, b, c, N);
printf("a: ");
for (int i = 0; i < N; i++) {
{BELOW_COUNT(10);}
printf("%d ", a[i]);
}
{BELOW_COUNT(11);}
printf("\nb: ");
for (int i = 0; i < N; i++) {
{BELOW_COUNT(12);}
printf("%d ", b[i]);
}
{BELOW_COUNT(13);}
printf("\nc: ");
for (int i = 0; i < N; i++) {
{BELOW_COUNT(14);}
printf("%d ", c[i]);
}
{BELOW_COUNT(15);}
printf("\n\n");
sleep(1);
}
{BELOW_COUNT(7);}
free(a);
free(b);
free(c);
return 0;
}The file below_instr.i contains the custom logic for counters:
below_instr.i
#ifndef BELOW_INSTR_I
#define BELOW_INSTR_I
#include "below_vendor.i"
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
// You may want to change the base type of the counters to smaller types
typedef long long below_counter_t;
below_counter_t* BELOW_counters = NULL;
// This will be called when the first BELOW_COUNT is called or when BELOW_finish is called if
// BELOW_COUNT is never called
void BELOW_init_custom(){
// Initialize the counters with BELOW_N_COUNTERS (defined in below_vendor.i)
BELOW_counters = (below_counter_t*)malloc(sizeof(below_counter_t) * BELOW_N_COUNTERS);
memset(BELOW_counters, 0, sizeof(below_counter_t)*BELOW_N_COUNTERS);
// BELOW_finish must be called before the program exits. It calls BELOW_finish_custom
// If atexit is not available, you may use BELOW_DYNAMIC_ANALYSIS definition in your original
// code to call BELOW_finish directly only in the dynamic analysis context
atexit(BELOW_finish);
}
void BELOW_COUNT_custom(int id) {
// Counters must be incremented here
BELOW_counters[id]++;
}
void BELOW_finish_custom() {
// When your run script is over, counters must be written to wedolow.prof file
// in the run script directory
// If you can't write files, you must find another way to print the counter and use your
// run script to read them and write them to wedolow.prof file
FILE* f = fopen("wedolow.prof", "w+");
// First write the number of counters
fprintf(f, "%d,", BELOW_N_COUNTERS);
// Then write the counters
for(int i=0; i<BELOW_N_COUNTERS; i++){
fprintf(f, "%lld,", BELOW_counters[i]);
}
fclose(f);
// Clean up before exit.
// Be sure that no counter increment is done after this point
free(BELOW_counters);
}
#endifKey elements inside below_instr.i:
Counters array and type:
typedef long long below_counter_t;
below_counter_t* BELOW_counters = NULL;Modify the type depending on your platform and expected maximum counter values.
Initialization function called on first count (or via atexit):
void BELOW_init_custom()Custom count function called on each instrumented point:
void BELOW_COUNT_custom(int id)Finish function that writes
wedolow.profby default:
void BELOW_finish_custom()below_vendor.i (vendor code) defines, among other things, the preprocessor variable:
#define BELOW_DYNAMIC_ANALYSISYou may use BELOW_DYNAMIC_ANALYSIS in your original code to alter behavior only in dynamic analysis mode. For example, the original infinite loop can be limited to a fixed number of iterations in dynamic analysis mode to allow the instrumented run to end and produce profiling output.
Example: original main modified for dynamic analysis to stop after 10 iterations
main.c (modified)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define N 5
void vect_add(int *a, int *b, int *c, int n) {
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
}
int main() {
int *a = malloc(N * sizeof(int));
int *b = malloc(N * sizeof(int));
int *c = malloc(N * sizeof(int));
if (!a || !b || !c) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
srand(time(NULL));
#ifdef BELOW_DYNAMIC_ANALYSIS
int loop_counter = 0;
#endif
while (1) {
for (int i = 0; i < N; i++) {
a[i] = rand() % 100;
b[i] = rand() % 100;
}
vect_add(a, b, c, N);
printf("a: ");
for (int i = 0; i < N; i++) printf("%d ", a[i]);
printf("\nb: ");
for (int i = 0; i < N; i++) printf("%d ", b[i]);
printf("\nc: ");
for (int i = 0; i < N; i++) printf("%d ", c[i]);
printf("\n\n");
sleep(1);
#ifdef BELOW_DYNAMIC_ANALYSIS
loop_counter++;
if (loop_counter >= 10) {
break; // Stop after 10 iterations
}
#endif
}
free(a);
free(b);
free(c);
return 0;
}The corresponding instrumented version includes the same BELOW_COUNT calls; when built with the dynamic-analysis-aware original code, the instrumented run will terminate after 10 iterations.
Build and run the instrumented example:
> gcc -O2 -o exec main.cpp
> ./exec
a: 4 40 69 24 32
b: 90 40 3 42 20
c: 94 80 72 66 52
[...]
a: 2 79 63 28 87
b: 27 77 91 93 30
c: 29 156 154 121 117When the instrumented execution stops, wedolow.prof is created. Example content:
wedolow.prof
16,1,10,50,10,0,1,10,1,50,10,50,10,50,10,50,10,The first number (16) is the number of counters (BELOW_N_COUNTERS).
Following numbers are the counter values recorded during the run.
Upload the wedolow.prof file via the beLow UI and click Run analysis. The analysis will incorporate the execution data.
Adapting manual dynamic analysis to your usage
For any project, the instrumented package contains:
A modified version of your project files (content depends on the project)
below_vendor.i(stable vendor content)below_instr.i(customizable)
All custom logic (counters allocation, initialization, termination) is in below_instr.i. In most cases you only need to modify this file and keep a copy in your project so you don't rewrite changes for each manual analysis.
Ensure your instrumented execution ends at some point. Use BELOW_DYNAMIC_ANALYSIS in your original code if needed to limit runtime.
Examples of custom modifications (illustrative of use cases present in the original content):
On microcontrollers, initialize UART and write profiling data to UART instead of a file, then copy-paste output into
wedolow.prof.If you have debug tools accessing program memory, read the counters array directly and format the data offline.
In both cases, modifying below_instr.i should be sufficient.
Upload profiling data
Once uploaded, click Run analysis. The analysis will use your profiling information.
Last updated 3 months ago
