Difference between revisions of "User:Argonel/bug"

(Rewrite some bits)
Line 22: Line 22:
 
* If you don't find any similar reports, assume the new bug report is unique
 
* If you don't find any similar reports, assume the new bug report is unique
 
* If you find a similar bug report there are two choices:
 
* If you find a similar bug report there are two choices:
** If you're certain it is the same issue, mark the report as duplicate. The bug report you initially picked is going to be marked as duplicate of the original report. If the duplicate has additional information, you may want to add it to the original. Note: some details may look unimportant to you, but they may be important for developers who know about the application workflow and code. Also, adding a big amount of minimal/incomplete information you may end up generating a big and complete testcase
+
** If you're certain it is the same issue, mark the report as duplicate. The bug report you initially picked is going to be marked as duplicate of the original report. If the duplicate has additional information, you may want to add it to the original. Note: some details may look unimportant to you, but they may be important for developers who know about the application workflow and code. By adding a big amount of minimal/incomplete information you may end up generating a big and complete testcase
 
** If you aren't completely sure, you need someone else to double-check your work. You may want to add a comment in the current report. Then, you should ask in the #kde-bugs IRC channel for someone to look at your comment.
 
** If you aren't completely sure, you need someone else to double-check your work. You may want to add a comment in the current report. Then, you should ask in the #kde-bugs IRC channel for someone to look at your comment.
 
*** Comment template:
 
*** Comment template:
Line 34: Line 34:
 
===For "normal" (non-crash) reports===
 
===For "normal" (non-crash) reports===
  
# Pick some "keywords" from the current report. These keywords need to explain the what the bug is about
+
# Pick some "keywords" from the current report. These keywords need to explain what the bug is about
# Perform a full search over the same product, initially in the "general" component. Put the keywords in the summary field, and perform the search
+
# Perform a full search over the same product, initially in the "general" component, including the keywords in the summary field
# If your search has results in it, check them all, reading the whole description and trying to identify the situation
+
# If your search rendered some results, check them all, reading the whole description and trying to identify the situation
 
# If you don't get any results, you need to go back and:
 
# If you don't get any results, you need to go back and:
#* Change your keywords (tip: select thesaurus, or similar/related concepts) or
+
#* Change your keywords (tip: check a thesaurus, or similar/related concepts) or
 
#* Use the keywords in the "Comments" field (so the search will look in the bug description and comments)
 
#* Use the keywords in the "Comments" field (so the search will look in the bug description and comments)
  
 
{{Note|When using more than one word in the "Comments" field, you may need to select the option "contains all of the words/strings"}}
 
{{Note|When using more than one word in the "Comments" field, you may need to select the option "contains all of the words/strings"}}
  
<!-- TODO reword this? -->
+
{{Note|It is sometimes difficult to choose the proper keywords, as different people might describe the same issue differently}}
{{Note|It is sometimes difficult to choose the proper ones, as the way of describing a scene varies from person to person}}
 
  
 
===For "crash" reports===
 
===For "crash" reports===
Line 63: Line 62:
 
A backtrace is a collection of information that describes what the application was doing when it encountered an error and had to close itself. In part, it contains a listing of the ''stack'' of functions involved in the crash.
 
A backtrace is a collection of information that describes what the application was doing when it encountered an error and had to close itself. In part, it contains a listing of the ''stack'' of functions involved in the crash.
  
In KDE applications, the backtraces are most often generated by the Crash Handler Dialog, aka “Dr Konqi”. They can also be generated by a general debugger such as “GDB”, but that involves more steps.
+
In KDE applications, the backtraces are most often generated by the Crash Handler Dialog, a.k.a. “Dr. Konqi”. They can also be generated by a general debugger such as “GDB”, but that involves more steps.
  
The backtrace is read from top to bottom. The first line typically indicates ''where'' the crash occurred (because of an illegal instruction, invalid pointer, memory problem or other issue). The other lines show the "path" leading to the crash from the beginning of execution.
+
The backtrace is read from top to bottom. The first line typically indicates ''where'' the crash occurred (because of an illegal instruction, invalid pointer, memory problem or another issue). The other lines show the "path" leading to the crash from the beginning of execution.
  
 
=====Example=====
 
=====Example=====
Line 90: Line 89:
 
  /source/code/file.cpp:linenumber
 
  /source/code/file.cpp:linenumber
  
* #NumberInTheStack: This the position of the call in the stack. Lower numbers are nearer to the point at which the crash occurred. This may not reach #1, especially with Dr. Konqi reports.
+
* #NumberInTheStack: This the position of the call in the stack. Lower numbers are nearer to the point at which the crash occurred. This may not reach #1, especially with Dr. Konqi reports
* MemoryAddress: This is not overly useful for triage
+
* MemoryAddress: This is not particularly useful for triage since it represents the memory of the user's machine
* Namespace: C++ namespace of the function. It may not be present if there are no namespaces. This could also be a class name if "Class" is a nested one.
+
* Namespace: C++ namespace of the function. It may not be present if there are no namespaces. This could also be a class name if the next component ("Class") is nested/inherited (a.k.a. a child class)
 
* Class: C++ class name of the function
 
* Class: C++ class name of the function
 
* FunctionMember: C++ function name
 
* FunctionMember: C++ function name
 
* argumentThis=pointerValue : this first argument is often the memory address/pointer of the C++ object (example: <code>"this=0x91ec5f8"</code>) - other arguments use the same form "parameterName=parameterValue"
 
* argumentThis=pointerValue : this first argument is often the memory address/pointer of the C++ object (example: <code>"this=0x91ec5f8"</code>) - other arguments use the same form "parameterName=parameterValue"
* (argument1=value, …): arguments supplied to the function. This information may not be available if '''debug information''' is not available
+
* (argument1=value, argument2=value, …): arguments supplied to the function. This information may not be available if debug symbols are missing
* path/to/source/code/file.cpp:linenumber the path to the source code file that describes that function and the line number. The path is the one found at '''build time'''. This information may not be useful if '''debug information''' is not available (in that case, the name of the library or application binary may be included. Example: ''/home/kde-devel/kde/lib/libsopranoclient.so.1'')
+
* path/to/source/code/file.cpp:linenumber : the path to the source code file that describes that function and the line number. The path is the one found at '''build time''', that is, it can be searched for in the source code. This information may not be useful if debug symbols are missing (in that case, the name of the library or application binary may be included instead. Example: ''/home/kde-devel/kde/lib/libsopranoclient.so.1'')
  
 
'''Example''':
 
'''Example''':
Line 108: Line 107:
 
* Function's name: "determineUri"
 
* Function's name: "determineUri"
 
* The "Nepomuk::ResourceData" object on which the "determineUri" function was called has the memory address "0x91ec5f8"
 
* The "Nepomuk::ResourceData" object on which the "determineUri" function was called has the memory address "0x91ec5f8"
* On line 671 of "/home/kde-devel/kde/src/KDE/kdelibs/nepomuk/core/resourcedata.cpp", the call to the 12th function can be found.
+
* On line 671 of "/home/kde-devel/kde/src/KDE/kdelibs/nepomuk/core/resourcedata.cpp", the call to the 12th function can be found
  
 
=====Identifying the first (useful) function=====
 
=====Identifying the first (useful) function=====
  
The first thing to do is locate where the crash occurred. If Dr. Konqi was used to capture the backtrace, the crashed thread will be identifable with the text <tt>[KCrash Handler]</tt>. This thread may be first, last - or in the middle.
+
The first thing to do is locate where the crash occurred. If Dr. Konqi was used to capture the backtrace, the crashed thread will be identified with the text <tt>[KCrash Handler]</tt>. This thread may be first, last or in the middle, but it's usually the last.
  
If the backtrace was generated a different way, it should still have some evidence of which thread crashed:
+
If the backtrace was generated using a different method (like '''gdb''' or '''coredumpctl'''), it should still have some evidence of which thread crashed:
  
 
* <code>[Current thread is 1 (Thread 0x7fb5a84e7940 (LWP 2045))]</code>
 
* <code>[Current thread is 1 (Thread 0x7fb5a84e7940 (LWP 2045))]</code>
Line 121: Line 120:
  
  
If the application only had one thread, then it is at the top of the unique thread. Otherwise you may need to look at all the threads (the <tt>KCrash Handler</tt> mark may not always be in thread number 1).
+
If the application only had one thread, then it is at the top of the unique thread. Otherwise you may need to look at all threads (the <tt>KCrash Handler</tt> mark may not always be in thread number 1).
  
 
Once the crashed thread is identified, pick up the first two or three "ClassName::Functions" pairs from top to bottom (some functions should be ignored, read below).
 
Once the crashed thread is identified, pick up the first two or three "ClassName::Functions" pairs from top to bottom (some functions should be ignored, read below).
Line 127: Line 126:
 
These pairs will be used as "keywords" for the duplicate search.
 
These pairs will be used as "keywords" for the duplicate search.
  
{{Note|This is only a general rule. There are some special cases when the first three functions at the top may be the same but the crash may be different (especially in complex application/libraries such as Konqueror)}}
+
{{Note|This is only a general rule. There are some special cases when the first three functions at the top may be the same but the crash may be different (especially in complex applications/libraries such as Konqueror)}}
  
If the first backtrace functions are not available (they are not there, or there are "??") then we cannot proceed without [[#Check_the_report_quality_.28and_ask_for_missing_information.29|asking for more information]] (a more complete backtrace).
+
If the first backtrace functions are not available (they are not there, or "??" are shown instead) then we cannot proceed without [[#Check_the_report_quality_.28and_ask_for_missing_information.29|asking for more information]] (a more complete backtrace).
  
 
=====Avoiding useless function calls=====
 
=====Avoiding useless function calls=====
Line 147: Line 146:
 
====X11 crashes====
 
====X11 crashes====
  
There are special crashes related to the X11 graphics server. To identify these crashes you can search for the "XIOError" function name (often on Thread 1). The <tt>[KCrash handler]</tt> mark appears in a secondary thread.
+
There are special crashes related to the X11 graphics platform. To identify these crashes you can search for the "XIOError" function name (often on Thread 1). The <tt>[KCrash handler]</tt> mark appears in a secondary thread.
  
 
The important thing in identifying these crashes is recognizing the functions '''below''' the XIOError call (which functions caused the X11 error).
 
The important thing in identifying these crashes is recognizing the functions '''below''' the XIOError call (which functions caused the X11 error).
Line 153: Line 152:
 
In most of these crashes the functions below <tt>[KCrash handler]</tt> are not important (but they could still be useful to search for duplicates).
 
In most of these crashes the functions below <tt>[KCrash handler]</tt> are not important (but they could still be useful to search for duplicates).
  
====Unintelligble functions====
+
====Unintelligible functions====
  
 
In some cases the names of the functions will be encoded (''mangled''). For example "<tt>_ZNK14QMessageLogger5fatalEPKcz</tt>" instead of "<tt>QMessageLogger::fatal(char const*, ...)</tt>". If this occurs, you can use the <tt>c++filt</tt> utility to convert the mangled names to a human-readable format.
 
In some cases the names of the functions will be encoded (''mangled''). For example "<tt>_ZNK14QMessageLogger5fatalEPKcz</tt>" instead of "<tt>QMessageLogger::fatal(char const*, ...)</tt>". If this occurs, you can use the <tt>c++filt</tt> utility to convert the mangled names to a human-readable format.
 +
 +
If you see yourself doing this often, you can use Kate's External Tools functionality to make the process easier. Just create an external tool with "c++filt" as the executable, "%{Document:FileName}" as argument, "Replace Current Document" as output, and add it to the toolbar. This way you can simply copy a backtrace, paste it in Kate and press a button to easily demangle it.
  
  
Line 175: Line 176:
 
* Hardware related functions use Solid classes [Bugzilla product: '''"solid"''']
 
* Hardware related functions use Solid classes [Bugzilla product: '''"solid"''']
 
* Power Management functions use PowerDevil [Bugzilla product: '''"solid"''', component: '''"powerdevil-daemon"''']
 
* Power Management functions use PowerDevil [Bugzilla product: '''"solid"''', component: '''"powerdevil-daemon"''']
 +
 +
<!-- This probably needs a full rewrite since neither Mandriva nor kdebase exist anymore; in the case of kdelibs there's only kdelibs4support which is for the transition from KDE4 to Plasma 5
  
 
==Debug package names for several distributions==
 
==Debug package names for several distributions==
Line 222: Line 225:
  
 
For a detailed list of distribution naming scheme examples you can look at [http://techbase.kde.org/index.php?title=User:DarioAndres/CreateUsefulReports#How_to_obtain_debug_package_for_several_distributions How to obtain debug packages for every distribution].
 
For a detailed list of distribution naming scheme examples you can look at [http://techbase.kde.org/index.php?title=User:DarioAndres/CreateUsefulReports#How_to_obtain_debug_package_for_several_distributions How to obtain debug packages for every distribution].
 +
-->

Revision as of 14:27, 21 February 2021

Note
This is work in progress. You can contribute to this page or request updates.


noframe
 
TODO
Fix anchor links, see eg "crash" reports section

Identifying duplicates

There are several ways of identifying duplicate reports depending on the kind of bug. In this guide we will discuss two kinds: normal, and crash.

General

  • A search for duplicates should initially be done against the product of the bug report you are triaging. If you don't find any related issues, you may need to search in a different product.
  • You may want to limit the results by date: you can select a starting date from some years (or months) ago
Ktip.png
 
Tip
You can search in multiple products at the same time.


Note
Due to the structure of KDE software, a bug reported for an application may be tracked in a library product (example, a bug in Plasma Desktop may be a bug in Plasma Workspace and therefore tracked in the "plasma-workspace" product)


Processing search results

  • If you don't find any similar reports, assume the new bug report is unique
  • If you find a similar bug report there are two choices:
    • If you're certain it is the same issue, mark the report as duplicate. The bug report you initially picked is going to be marked as duplicate of the original report. If the duplicate has additional information, you may want to add it to the original. Note: some details may look unimportant to you, but they may be important for developers who know about the application workflow and code. By adding a big amount of minimal/incomplete information you may end up generating a big and complete testcase
    • If you aren't completely sure, you need someone else to double-check your work. You may want to add a comment in the current report. Then, you should ask in the #kde-bugs IRC channel for someone to look at your comment.
      • Comment template:
        This bug looks related to bug XXXXXX
        (where XXXXXX is the bug ID of "main" bug)


Note
You may find related reports that are already marked as duplicate of a third report. Always try to use this third report as the main one (resolve the duplicates chain). However, in some cases the main report refers to a root issue and some of its duplicates may refer to sub-issues. In those cases try to check which one refers to the issue you are looking at.


For "normal" (non-crash) reports

  1. Pick some "keywords" from the current report. These keywords need to explain what the bug is about
  2. Perform a full search over the same product, initially in the "general" component, including the keywords in the summary field
  3. If your search rendered some results, check them all, reading the whole description and trying to identify the situation
  4. If you don't get any results, you need to go back and:
    • Change your keywords (tip: check a thesaurus, or similar/related concepts) or
    • Use the keywords in the "Comments" field (so the search will look in the bug description and comments)
Note
When using more than one word in the "Comments" field, you may need to select the option "contains all of the words/strings"


Note
It is sometimes difficult to choose the proper keywords, as different people might describe the same issue differently


For "crash" reports

  1. Perform the same operation as with normal bug reports
  2. Check for reports with duplicate backtraces: (Read the Backtraces section below)

Perform a full search over the same product, initially in the "general" component, putting the "ClassName::FunctionName" pairs that identify the crash in the Comments field of the form (if you put more than one pair, you need to select the option "contains all of the words/strings")

Note
This is why the backtrace needs to be pasted in as a comment - you can't search file attachment contents


Identifying duplicates (crashes)

C++ Backtraces

Definition

A backtrace is a collection of information that describes what the application was doing when it encountered an error and had to close itself. In part, it contains a listing of the stack of functions involved in the crash.

In KDE applications, the backtraces are most often generated by the Crash Handler Dialog, a.k.a. “Dr. Konqi”. They can also be generated by a general debugger such as “GDB”, but that involves more steps.

The backtrace is read from top to bottom. The first line typically indicates where the crash occurred (because of an illegal instruction, invalid pointer, memory problem or another issue). The other lines show the "path" leading to the crash from the beginning of execution.

Example
Note
In Bugzilla these will typically be line wrapped as seen in the next section


Application: Plasma Workspace (kdeinit4), signal: Bus error
[KCrash Handler]
#5  0x00007fb563bb8f02 in KPixmapCache::Private::mmapFile (this=0x92df60, filename=..., info=0x92dfb0, newsize=33656832) at /usr/src/debug/kdelibs-4.4.1/kdeui/util/kpixmapcache.cpp:491
#6  0x00007fb563be3c34 in KPixmapCache::Private::mmapFiles (this=0x92df60) at /usr/src/debug/kdelibs-4.4.1/kdeui/util/kpixmapcache.cpp:419
#7  0x00007fb563be38e3 in KPixmapCache::Private::init (this=0x92df60) at /usr/src/debug/kdelibs-4.4.1/kdeui/util/kpixmapcache.cpp:1061
#8  0x00007fb563be576d in KPixmapCache::discard (this=0x1203ca0) at /usr/src/debug/kdelibs-4.4.1/kdeui/util/kpixmapcache.cpp:1279
#9  0x00007fb563be5e48 in KPixmapCache::deleteCache (name=...) at /usr/src/debug/kdelibs-4.4.1/kdeui/util/kpixmapcache.cpp:1255
#10 0x00007fb55afdc97d in Plasma::ThemePrivate::discardCache (this=0x7a7d30) at /usr/src/debug/kdelibs-4.4.1/plasma/theme.cpp:224
#11 0x00007fb55afe009b in Plasma::ThemePrivate::setThemeName (this=0x7a7d30, tempThemeName=<value optimized out>, writeSettings=<value optimized out>) at /usr/src/debug/kdelibs-4.4.1/plasma/theme.cpp:380
#12 0x00007fb55afe19fb in Plasma::Theme::settingsChanged (this=0x70af20) at /usr/src/debug/kdelibs-4.4.1/plasma/theme.cpp:341
#13 0x00007fb55afe2918 in Plasma::ThemePrivate::settingsFileChanged (this=0x7a7d30, file=<value optimized out>) at /usr/src/debug/kdelibs- 4.4.1/plasma/theme.cpp:335
...
Breakdown of a backtrace line
#NumberInTheStack MemoryAddress in Namespace::Class::FunctionMember 
(argumentThis=pointerValue, argument1=value, argument2=value, ...) at path/to
/source/code/file.cpp:linenumber
  • #NumberInTheStack: This the position of the call in the stack. Lower numbers are nearer to the point at which the crash occurred. This may not reach #1, especially with Dr. Konqi reports
  • MemoryAddress: This is not particularly useful for triage since it represents the memory of the user's machine
  • Namespace: C++ namespace of the function. It may not be present if there are no namespaces. This could also be a class name if the next component ("Class") is nested/inherited (a.k.a. a child class)
  • Class: C++ class name of the function
  • FunctionMember: C++ function name
  • argumentThis=pointerValue : this first argument is often the memory address/pointer of the C++ object (example: "this=0x91ec5f8") - other arguments use the same form "parameterName=parameterValue"
  • (argument1=value, argument2=value, …): arguments supplied to the function. This information may not be available if debug symbols are missing
  • path/to/source/code/file.cpp:linenumber : the path to the source code file that describes that function and the line number. The path is the one found at build time, that is, it can be searched for in the source code. This information may not be useful if debug symbols are missing (in that case, the name of the library or application binary may be included instead. Example: /home/kde-devel/kde/lib/libsopranoclient.so.1)

Example:

#13 0xb759d5d7 in Nepomuk::ResourceData::determineUri (this=0x91ec5f8) at /home/kde-devel/kde/src/KDE/kdelibs/nepomuk/core/resourcedata.cpp:671
  • The function call is the 13th in the stack
  • Function's namespace: "Nepomuk"
  • Function's class: "ResourceData"
  • Function's name: "determineUri"
  • The "Nepomuk::ResourceData" object on which the "determineUri" function was called has the memory address "0x91ec5f8"
  • On line 671 of "/home/kde-devel/kde/src/KDE/kdelibs/nepomuk/core/resourcedata.cpp", the call to the 12th function can be found
Identifying the first (useful) function

The first thing to do is locate where the crash occurred. If Dr. Konqi was used to capture the backtrace, the crashed thread will be identified with the text [KCrash Handler]. This thread may be first, last or in the middle, but it's usually the last.

If the backtrace was generated using a different method (like gdb or coredumpctl), it should still have some evidence of which thread crashed:

  • [Current thread is 1 (Thread 0x7fb5a84e7940 (LWP 2045))]
  • Thread 1 "kwin_x11" received signal SIGABRT, Aborted.
  • Process 7254 (plasmashell) of user 1000 dumped core.


If the application only had one thread, then it is at the top of the unique thread. Otherwise you may need to look at all threads (the KCrash Handler mark may not always be in thread number 1).

Once the crashed thread is identified, pick up the first two or three "ClassName::Functions" pairs from top to bottom (some functions should be ignored, read below).

These pairs will be used as "keywords" for the duplicate search.

Note
This is only a general rule. There are some special cases when the first three functions at the top may be the same but the crash may be different (especially in complex applications/libraries such as Konqueror)


If the first backtrace functions are not available (they are not there, or "??" are shown instead) then we cannot proceed without asking for more information (a more complete backtrace).

Avoiding useless function calls

Some functions or calls are common to a lot of applications using the same core libraries (like the Qt library, glib, glibc, or many others). These kinds of functions should not be used for searching as they are not representative of the crash itself and the search may return lots of results.

Classes and functions to ignore in a backtrace:

  • Kernel/GLibC functions (__kernel_vsyscall, raise, abort)
  • Functions from core/base libraries (libraries with filenames like libpthread.so.0, libc.so, libstdc++.so, libglib-2.0.so; or functions starting with "*__GI_"). You may also need to ignore calls to graphics drivers (like nvidia or libGL)
  • Qt container classes (QMap, QList, QLinkedList, QVector, QStack, QQueue, QSet, QMap, QMultiMap, QHash, QMultiHash)
  • Qt deep core classes (QApplication, QCoreApplication, QBasicAtomicInt, QBasicAtomicPointer, QAtomicInt, QAtomicPointer, QMetaObject, QPointer, QWeakPointer, QSharedPointer, QScopedPointer, QMetaCallEvent)
  • Qt misc functions (qt_message_output, qt_message, qGetPtrHelper, functions starting with qt_meta_)
  • Threads that "start" with __poll or pthread_cond_wait are probably not useful

Special cases (advanced)

X11 crashes

There are special crashes related to the X11 graphics platform. To identify these crashes you can search for the "XIOError" function name (often on Thread 1). The [KCrash handler] mark appears in a secondary thread.

The important thing in identifying these crashes is recognizing the functions below the XIOError call (which functions caused the X11 error).

In most of these crashes the functions below [KCrash handler] are not important (but they could still be useful to search for duplicates).

Unintelligible functions

In some cases the names of the functions will be encoded (mangled). For example "_ZNK14QMessageLogger5fatalEPKcz" instead of "QMessageLogger::fatal(char const*, ...)". If this occurs, you can use the c++filt utility to convert the mangled names to a human-readable format.

If you see yourself doing this often, you can use Kate's External Tools functionality to make the process easier. Just create an external tool with "c++filt" as the executable, "%{Document:FileName}" as argument, "Replace Current Document" as output, and add it to the toolbar. This way you can simply copy a backtrace, paste it in Kate and press a button to easily demangle it.


List of related KDE technologies

Warning
This section needs improvements: Please help us to

cleanup confusing sections and fix sections which contain a todo


  • Every KDE application use kdelibs [Bugzilla product: "kdelibs"]
  • Applications using the standard KDE file operations use KIO [Bugzilla product: "kio"] and probably KFile (for the UI part) [Bugzilla product: "kfile"]
  • Oxygen widget style (default) [Bugzilla product: "oxygen" component "style"] (component is included because "Oxygen" also refers to Plasma and icon themes)
  • Multimedia usage: Phonon library [Bugzilla product: "Phonon"]
  • PIM related applications use kdepimlibs, Akonadi and kresources technologies [Bugzilla products: "kdepim", "kdepimlibs", "Akonadi", "kresources"]
  • Applications using KHTML [Bugzilla product: konqueror"]
  • Applications using OpenDesktop services uses Attica [Bugzilla product: "attica"]
  • Screen management related operations use the Kephal subsystem [Bugzilla product: "kephal"]
  • Games use libkdegames [Bugzilla product: "libkdegames"]
  • Scanning related applications probably use the KSane lib [Bugzilla product: "libksane"]
  • Multimedia applications reading audio tags use taglib [Bugzilla product: "taglib"]
  • Hardware related functions use Solid classes [Bugzilla product: "solid"]
  • Power Management functions use PowerDevil [Bugzilla product: "solid", component: "powerdevil-daemon"]



This page was last edited on 21 February 2021, at 14:27. Content is available under Creative Commons License SA 4.0 unless otherwise noted.