LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 06-01-2022, 04:23 PM   #1
cyent
Member
 
Registered: Aug 2001
Location: ChristChurch New Zealand
Distribution: Ubuntu
Posts: 398

Rep: Reputation: 87
C/C++ bug finding / prevention tools?


Open source grows slowly..... but over an incredibly broad front... So it's easy to miss something new.

What C/C++ bug finding / bug prevention tools/techniques do you use or like?

I use currently...

* splint - static analysis - considering dropping in in favour of..
* gcc -Wall -W and -fanalyser and -fsanitize
* gdb (plays nice with emacs)
* gcov
* valgrind - This is an absolute treasure, if you're not using it and you're programming in C/C++. Drop whatever you're doing and wire this into your unit test setup now.
* unit testing.



In the past I have used libefence, but wrote something very similar that was lightweight enough to run on our embedded target. (Basically inflate each allocation enough to store metadata at the front and a cookie fore and after, and check the cookies haven't been eaten on free(), and corrupt the free'd memory to catch use after free's.)

What do you use?
 
Old 06-01-2022, 05:51 PM   #2
dugan
LQ Guru
 
Registered: Nov 2003
Location: Canada
Distribution: distro hopper
Posts: 11,246

Rep: Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323Reputation: 5323
I use clang-tidy. I'm aware that some places uses SonarQube.
 
Old 06-14-2022, 07:38 PM   #3
sundialsvcs
LQ Guru
 
Registered: Feb 2004
Location: SE Tennessee, USA
Distribution: Gentoo, LFS
Posts: 10,679
Blog Entries: 4

Rep: Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947
The primary technique that I use – in every language not just C/C++ – is to "code suspiciously." Functions and subroutines never "trust" their inputs. switch statements that are "always supposed to find a match" nevertheless are programmed to throw a meaningful exception if they don't.

I often use module-specific Exception subclasses just to help me to zero in on where an exception came from.

Calling modules are also "suspicious." They anticipate exceptions, catch them, add further identifying information to their messages, and re-throw them.

Of course, "if everything is working properly, this code is never executed." Which is exactly why it is there.

I have very quickly found and extinguished some very hard-to-find bugs in my code in this way.

The assert() macro is often used to do this, and the capability exists to omit these tests categorically through the use of an appropriate compile-time option when compiling "production" code, but I never omit them. My code goes out the door and straight into "production" with all of these suspicious safeguards in place. (And for what it's worth, I rarely use assert(). I prefer something more descriptive.)

Basically, "I never use a 'debugger.'" In all these years, I almost never have. This way is better. The party that is in the best position to realize that something is wrong is the software itself. "Don't trust ... Verify."

Last edited by sundialsvcs; 06-14-2022 at 07:48 PM.
 
1 members found this post helpful.
Old 06-14-2022, 08:19 PM   #4
evo2
LQ Guru
 
Registered: Jan 2009
Location: Japan
Distribution: Mostly Debian and CentOS
Posts: 6,724

Rep: Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705Reputation: 1705
Quote:
Originally Posted by sundialsvcs View Post
The primary technique that I use – in every language not just C/C++ – is to "code suspiciously."
In my experience this is usually refered to as "defensive programming". That term may be helpful if OP wants to search and read more on the topic.

Evo2.
 
1 members found this post helpful.
Old 06-14-2022, 09:01 PM   #5
cyent
Member
 
Registered: Aug 2001
Location: ChristChurch New Zealand
Distribution: Ubuntu
Posts: 398

Original Poster
Rep: Reputation: 87
Quote:
Originally Posted by evo2 View Post
In my experience this is usually refered to as "defensive programming". That term may be helpful if OP wants to search and read more on the topic.

Evo2.
Hmm. What the previous poster said was more or less like Design by Contract, which is how I write everything.

Unfortunately I have found a lot of "defensive programmers" who claim to be doing defensive programming (hopefully not you) have failed to understand DbC.

This results in insanity like checking parameters and returning error codes up the stack, which don't know what to do about them, up and and up until it reaches some level and they cast it to void!

AARRRGH! Hundreds of lines of code and CPU cycles whose net effect is to hide bugs!

Sorry. One of my pet hates.

But back to the previous posters switch case example, yes, I code so between splint and compiler warnings, if I don't handle _all_ values of an enum AND have a default, my build fails.

> They anticipate exceptions, catch them, add further identifying information to their messages, and re-throw them.

Yup. I try to code so if you see one of my error messages, you should both know exactly what went wrong AND how to fix it.
 
1 members found this post helpful.
Old 06-15-2022, 05:48 AM   #6
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,976

Rep: Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337
There are three types of programming "styles":
1. worst case programming: you prepare your code to handle all the possible and impossible error cases and situations
2. best case programming: you implement a running code which will work only if the input parameters and the environment are correct, otherwise will crash
3. single case programming: you stop developing code when the executed program completed first time (and have no idea if it will succeed next time).

To catch errors/bugs you have static code analyzers, dynamic analyzers, you can implement correct error handling and informative logging/reporting.
 
Old 06-15-2022, 08:00 PM   #7
sundialsvcs
LQ Guru
 
Registered: Feb 2004
Location: SE Tennessee, USA
Distribution: Gentoo, LFS
Posts: 10,679
Blog Entries: 4

Rep: Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947
@pan64, et seq: I really don't know what people "call it" these days, but my strategy is simply to assume that "the software, itself" is the only party that's really in the best position to realize that something is wrong.

For example, in a recursive-descent language compiler that I once wrote, there was a necessary subroutine which was supposed to replace a "NOP" instruction – provided for the occasion – with a "JMP" to a certain place. Well, "suspicious me" added a check to be sure that the instruction that was about to be replaced really was a "NOP." Well then, one day a customer tried to compile a "really-big job" – big enough to overflow the then-defined instruction address-space – and this exception-check went off because the instruction-writing routines had "wrapped around." This enabled me to reconstruct what had just happened, and to rearrange the system to accommodate. Had this check not been there, I might never have found the root cause of the problem ... a problem which had nothing to do with any of the subroutines involved.

There was nothing "wrong" with any of their code as-written, as long as the conditions which they presumed still held.

So: "Never 'Presume.'" The (precious, to most males ...) hair-follicles that you save will be your own.

I actually "glommed" this from a past life – where a vexing and mysterious problem in a "print-queue management routine" turned out to be caused by such a wrap-around. The code which "updated" a print-queue entry actually replaced it, without first checking that the entry it was about to replace was actually (still ...) the entry that it expected it to be. The permanent-fix turned out to be about twelve lines of (IBM assembler) code, as soon as the problem was recognized. "Aye, there's the rub." Only the computer could see it.

Last edited by sundialsvcs; 06-15-2022 at 08:09 PM.
 
Old 06-15-2022, 08:19 PM   #8
cyent
Member
 
Registered: Aug 2001
Location: ChristChurch New Zealand
Distribution: Ubuntu
Posts: 398

Original Poster
Rep: Reputation: 87
Quote:
Originally Posted by pan64 View Post
There are three types of programming "styles":
1. worst case programming: you prepare your code to handle all the possible and impossible error cases and situations
If the data is coming from an external untrusted source. eg Off the internet or over RF or from a user, do this one. (But you still have to trust your CPU and compiler. Trying to second guess them leads to madness)

A useful guideline at this level is "Parse, Don't Validate". https://lexi-lambda.github.io/blog/2...on-t-validate/

Skip to the "The danger of validation" section if Haskell gives you indigestion.

Quote:
2. best case programming: you implement a running code which will work only if the input parameters and the environment are correct, otherwise will crash
If the data is coming from higher routines whose very purpose and existence is to sanitize the inputs, and form correct inputs to guarantee that _this_ routine will work..... Do this one. Enables much simpler code and enables you to find and fix bugs in those higher level routines faster.

Quote:
3. single case programming: you stop developing code when the executed program completed first time (and have no idea if it will succeed next time).
I only ever do this if my purpose is to greater a once off "pretty" graphics display where my personal tastes define pretty.


It is a vital life skill to be able to work out when you're doing the first sort of programming and when you are doing the second.

Last edited by cyent; 06-15-2022 at 08:20 PM.
 
Old 06-16-2022, 08:40 AM   #9
sundialsvcs
LQ Guru
 
Registered: Feb 2004
Location: SE Tennessee, USA
Distribution: Gentoo, LFS
Posts: 10,679
Blog Entries: 4

Rep: Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947
The key idea I'm pushing here is that the code is written to be "suspicious" of bugs within itself. By constantly checking its assumptions, it is designed to reveal the fact that some other part of the program – itself – must be wrong. And to point out as specifically as possible what is wrong and where.

As we all know, the first and perhaps the worst part of "debugging" is realizing that a bug exists. The second task is then to locate it. "The software, itself" is in the very best position to do that. As it were, "from the inside." Write all sorts of self-defensive checks, and leave them in. If the trap never goes off, and it is correctly written, then this is a positive indicator that this bug doesn't exist (yet). The problem that you're looking for, whatever it is, must be someplace else.

I find "debuggers" to be crude tools and almost never use them – or, need to.

Last edited by sundialsvcs; 06-16-2022 at 08:42 AM.
 
Old 06-16-2022, 10:12 AM   #10
EdGr
Senior Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 1,000

Rep: Reputation: 472Reputation: 472Reputation: 472Reputation: 472Reputation: 472
Programs can also be designed in a manner that is unlikely to break. For example, the use of dynamically-allocated arrays and linked lists precludes buffer overrun bugs. The use of "long int" makes arithmetic overflow unlikely. Explicit length is more reliable than delimiters. Less state has fewer chances for inconsistency than more state.
Ed
 
Old 06-16-2022, 11:24 AM   #11
sundialsvcs
LQ Guru
 
Registered: Feb 2004
Location: SE Tennessee, USA
Distribution: Gentoo, LFS
Posts: 10,679
Blog Entries: 4

Rep: Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947
Yes, and if a subroutine handed me an empty dynamic array that wasn't supposed to be empty, my code is going to test for that and throw an Exception. And, so on. So, if at some point I'm looking for a bug, I can eliminate the possibility that this subroutine was handed an empty array. The bug has to be somewhere else.

Also, I fully agree with the admonition to use "container classes" instead of "roll your own." These classes are "known good," and might use advanced techniques that you ordinarily wouldn't bother with. That's also why I tell people, "use C++ instead of C." The resulting code is every bit as "tight" and efficient, but the language environment does much more of the "heavy lifting" for you, using known-good libraries. It's silly to be looking for a bug in a hand-rolled implementation of a linked list, once you have graduated from high school.

Quote:
Actum Ne Agas: "Do Not Do A Thing Already Done."

Last edited by sundialsvcs; 06-16-2022 at 11:26 AM.
 
Old 06-16-2022, 04:02 PM   #12
cyent
Member
 
Registered: Aug 2001
Location: ChristChurch New Zealand
Distribution: Ubuntu
Posts: 398

Original Poster
Rep: Reputation: 87
Quote:
Originally Posted by sundialsvcs View Post
The key idea I'm pushing here is that the code is written to be "suspicious" of bugs within itself. By constantly checking its assumptions, it is designed to reveal the fact that some other part of the program – itself – must be wrong. And to point out as specifically as possible what is wrong and where.

It's sounds like you're have caught the tail of the idea of design by contract.... but haven't quite taken taken it on board.

The core idea of design by contract is you define AND check what the preconditions are for this routine to work, and then define the postconditions that will be established by the routine.

Sadly writing a full postcondition is usually about as much work (or more!) as writing the routine, but if you move it into your unit test, you can write and much much simpler postcondition instantiated to _that_ particular testcases inputs.

The next step up is to work out what relationship must exist between instance variables of a class, so that the public methods (assuming they're invoked with the correct inputs) will _always_ work, and then write a class invariant check and invoke it at the end of the constructor start and end of _every_ public method and at the start of the destructor. Once you have the class working, you can switch the check off in any hot spots causing too much cpu load.

https://www.artima.com/articles/the-c-style-sweet-spot
 
Old 06-17-2022, 12:05 AM   #13
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 21,976

Rep: Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337Reputation: 7337
Quote:
Originally Posted by cyent View Post
public methods (assuming they're invoked with the correct inputs) will _always_ work,
Never assume anything, this is the very first source of unpredictable results (the second is the lack of knowledge when a developer doesn’t really know how written code works, what it actually does, and what it never does).
 
Old 06-17-2022, 01:25 PM   #14
sundialsvcs
LQ Guru
 
Registered: Feb 2004
Location: SE Tennessee, USA
Distribution: Gentoo, LFS
Posts: 10,679
Blog Entries: 4

Rep: Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947Reputation: 3947
@cyent, my practices may be similar to "design by contract," but I quite honestly believe that "design by contract" in actual practice sinks because it asks too much in the design stage and burdens too much the implementation stage – with uncertain actual success.

What I very simply do is to write code that is brimming with "suspicious" tests, and I never remove the code from the system. It is out there in production, doing its job. Inevitably, a trap finally goes off, "out of the blue." But by doing so it has just fulfilled the first reason why it is there: "now you know that you have a problem." As I've said, the first problem with computer software malfunction is simply realizing that it is malfunctioning. The second problem is to locate it. But if you simply look at "external behavior," you might never find it – even if the underlying problem was of such a nature that it visibly and meaningfully affected "external behavior" at all.

Maybe that's why all modern automobiles have standard self-monitoring and event-recording software which superficially manifests itself as the "check engine light," while it is also accumulating an event-log which a mechanic can download and review. (And, if you are cursed to live in a part of the country where you face "emissions tests" when renewing your car tags, that's 99% of what they do, as well.) The point is, of course, that, without that little light, you might not be aware that there is a serious and growing problem with your car, until it leaves you stranded by the side of the road.

Last edited by sundialsvcs; 06-17-2022 at 01:27 PM.
 
Old 06-17-2022, 02:53 PM   #15
EdGr
Senior Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 1,000

Rep: Reputation: 472Reputation: 472Reputation: 472Reputation: 472Reputation: 472
Warnings are good, but better would be if the developers actually fixed them.

Here is the current disaster spewing out of Firefox:

Code:
% firefox
ATTENTION: default value of option mesa_glthread overridden by environment.
ATTENTION: default value of option mesa_glthread overridden by environment.
ATTENTION: default value of option mesa_glthread overridden by environment.
ATTENTION: default value of option mesa_glthread overridden by environment.
[Child 6257, Main Thread] WARNING: g_object_set: assertion 'G_IS_OBJECT (object)' failed: 'glib warning', file /tmp/firefox-101.0/toolkit/xre/nsSigHandlers.cpp:167

(/usr/lib64/firefox/firefox-bin:6257): GLib-GObject-CRITICAL **: 12:49:48.236: g_object_set: assertion 'G_IS_OBJECT (object)' failed
[Child 6307, Main Thread] WARNING: g_object_set: assertion 'G_IS_OBJECT (object)' failed: 'glib warning', file /tmp/firefox-101.0/toolkit/xre/nsSigHandlers.cpp:167

(/usr/lib64/firefox/firefox-bin:6307): GLib-GObject-CRITICAL **: 12:49:48.441: g_object_set: assertion 'G_IS_OBJECT (object)' failed
Missing chrome or resource URL: resource://gre/modules/UpdateListener.jsm
[Child 6373, Main Thread] WARNING: g_object_set: assertion 'G_IS_OBJECT (object)' failed: 'glib warning', file /tmp/firefox-101.0/toolkit/xre/nsSigHandlers.cpp:167

(/usr/lib64/firefox/firefox-bin:6373): GLib-GObject-CRITICAL **: 12:49:49.239: g_object_set: assertion 'G_IS_OBJECT (object)' failed
[Child 6376, Main Thread] WARNING: g_object_set: assertion 'G_IS_OBJECT (object)' failed: 'glib warning', file /tmp/firefox-101.0/toolkit/xre/nsSigHandlers.cpp:167

(/usr/lib64/firefox/firefox-bin:6376): GLib-GObject-CRITICAL **: 12:49:49.243: g_object_set: assertion 'G_IS_OBJECT (object)' failed
[Child 6380, Main Thread] WARNING: g_object_set: assertion 'G_IS_OBJECT (object)' failed: 'glib warning', file /tmp/firefox-101.0/toolkit/xre/nsSigHandlers.cpp:167

(/usr/lib64/firefox/firefox-bin:6380): GLib-GObject-CRITICAL **: 12:49:49.251: g_object_set: assertion 'G_IS_OBJECT (object)' failed
[Child 6471, Main Thread] WARNING: g_object_set: assertion 'G_IS_OBJECT (object)' failed: 'glib warning', file /tmp/firefox-101.0/toolkit/xre/nsSigHandlers.cpp:167

(/usr/lib64/firefox/firefox-bin:6471): GLib-GObject-CRITICAL **: 12:49:53.814: g_object_set: assertion 'G_IS_OBJECT (object)' failed
Missing chrome or resource URL: resource://gre/modules/UpdateListener.jsm

###!!! [Parent][PImageBridgeParent] Error: RunMessage(msgname=PImageBridge::Msg_WillClose) Channel closing: too late to send/recv, messages will be lost

%
Ed
 
  


Reply

Tags
analysis, bugs, tools



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] Deauthentication attacks - prevention ? your favorite detection tools. mazinoz Linux - Security 7 12-21-2015 04:54 PM
Ok gurus, newbie needs mad prevention and detection system patientzero Linux - Security 5 09-03-2003 05:30 AM
Basic intrustion detection/prevention jamesrh Linux - Networking 5 05-18-2003 02:25 PM
Email spamming prevention m_sree Linux - Security 4 01-16-2003 08:54 AM
X Window Autostart Prevention Stephanie Linux - General 4 05-12-2001 01:12 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 07:18 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration